cdp-toolkit 1.9.7 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js CHANGED
@@ -5208,21 +5208,21 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
5208
5208
  var MANIFEST = [
5209
5209
  {
5210
5210
  name: "list_pages",
5211
- description: "Enumerate browser page targets via the CDP browser endpoint (GET /json/list). By default returns only page-type tabs; set 'all' to also include workers and background pages. Each entry carries the targetId used as a target selector elsewhere, plus an 'origin' field recording where the tab came from: 'agent' means this toolkit created it (the entry then also carries the creating 'label' and 'createdAt'), and this stays true after the creating agent releases its lease or dies, which is what makes a stray agent tab findable later. 'unknown' means there is no creation record. It never says 'human': the toolkit cannot prove a person opened a tab, so 'unknown' is the honest word for a tab it did not create, one opened before the toolkit ran, or one whose record could not be written. A tab whose record exists but could not be read reports origin 'unknown' plus 'originUnreadable', so a broken record is never mistaken for no record. Under CDP_REQUIRE_LEASE this call also reaps abandoned agent tabs, but reap-CLOSE and lease-RECLAIMABLE (list_leases' 'stale') now fire at different times: a lease reads reclaimable the moment its TTL elapses, cheap and reversible since another agent may simply take it, while this call only CLOSES the tab, destructively, after an ADDITIONAL grace period past that TTL (CDP_REAP_GRACE_MS, default 2700000ms/45 minutes, so 60 minutes total after last use) — a tab an agent is mid-build between calls on is not destroyed out from under it. A dead-pid lease is reaped immediately regardless of grace: that process is never coming back. Closed tabs are reported in an additive 'reaped' array ({targetId,label,reason}) present only when something was actually closed; a tab with no lease is never reaped. Every entry under an active, readable lease of this backend also carries 'lease': {label, pid, idleMs, expiresAt, stale} — idleMs and expiresAt are computed fresh on every call (now-lastUsedAt and lastUsedAt+ttlMs), never read off disk, and this is unconditional on 'probe'. Set 'probe' to true to also ping each page-type target's renderer: one bounded (500ms) check per target, never more, so a single wedged tab cannot stall the listing beyond its own budget. This adds 'responsive' (true iff the ping answered in time; false on a timeout, a page-side exception, or an unreachable target — never an error for the whole call) and, where that same round trip found human-attributed input, 'humanActiveMs' (see claim_page and list_leases for what that field means and cannot prove). 'responsive' and 'humanActiveMs' are both ABSENT — never false/null — when 'probe' is not set, and 'responsive' is likewise absent on a backend that cannot answer this ping at all.",
5211
+ description: "Enumerate browser page targets (GET /json/list); page-type tabs only unless all=true. Each entry carries its targetId plus an origin field: 'agent' means this toolkit created it (also carries the creating label/createdAt, and stays 'agent' after the lease is released or the creator dies, making stray agent tabs findable), 'unknown' means no creation record - it never says 'human', the honest word for any tab it did not create; a record that exists but won't read adds originUnreadable. An entry under an active, readable lease also carries a 'lease' object {label,pid,idleMs,expiresAt,stale}, with idleMs and expiresAt computed fresh every call (never off disk), independent of probe. Under CDP_REQUIRE_LEASE this call also reaps abandoned agent tabs into an additive reaped array - but a lease reads reclaimable the moment its TTL elapses, while the tab is CLOSED only after a further grace (CDP_REAP_GRACE_MS, default 2700000ms/45min, so 60min total after last use); a dead-pid lease is reaped at once, a tab with no lease never. With probe, each page-type entry also gets 'responsive' (and humanActiveMs where human input is seen); both are absent, never false/null, without probe.",
5212
5212
  inputSchema: {
5213
5213
  type: "object",
5214
5214
  properties: {
5215
5215
  lease: {
5216
5216
  type: "string",
5217
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5217
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5218
5218
  },
5219
5219
  all: {
5220
5220
  type: "boolean",
5221
- description: "Include non-page targets (service/shared workers, background pages) when true; otherwise only page-type tabs are listed."
5221
+ description: "Include non-page targets (service/shared workers, background pages); default false lists page-type tabs only."
5222
5222
  },
5223
5223
  probe: {
5224
5224
  type: "boolean",
5225
- description: "Ping each page-type target's renderer before returning: one bounded (500ms) Runtime.evaluate per target, never more. Adds 'responsive' to every page-type entry and, where the same round trip finds human-attributed input, 'humanActiveMs'. A wedged or unreachable tab reports responsive:false rather than failing the call. Defaults to false, which is byte-identical to the pre-1.8.0 shape."
5225
+ description: "Ping each page-type renderer before returning, one bounded 500ms check per target and never more, so a wedged tab can't stall the listing. Adds 'responsive' (false on timeout/exception/unreachable, never an error for the whole call) and, where the same round trip finds human input, humanActiveMs. Default false. On a backend that cannot answer the ping, responsive is absent too (not false)."
5226
5226
  }
5227
5227
  },
5228
5228
  required: [],
@@ -5237,7 +5237,7 @@ var MANIFEST = [
5237
5237
  properties: {
5238
5238
  lease: {
5239
5239
  type: "string",
5240
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5240
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5241
5241
  },
5242
5242
  url: {
5243
5243
  type: "string",
@@ -5245,15 +5245,15 @@ var MANIFEST = [
5245
5245
  },
5246
5246
  claim: {
5247
5247
  type: "boolean",
5248
- description: "Claim the new tab atomically as part of creating it and return a lease token alongside {targetId,url}. Default false, which is byte-identical to the pre-1.2 behavior. Under CDP_REQUIRE_LEASE the new tab is claimed and a lease returned even without claim:true, because a tab nobody holds is a tab nobody may drive; passing claim:true additionally makes the lease an explicit one, which requires its token on every later call even from this same process."
5248
+ description: "Claim the new tab atomically and return a lease token alongside {targetId,url}. Under CDP_REQUIRE_LEASE the tab is claimed and a lease returned even without claim:true; claim:true makes it an EXPLICIT lease that demands its token on every later call, even from this process. Default false."
5249
5249
  },
5250
5250
  label: {
5251
5251
  type: "string",
5252
- description: "Agent label recorded on the lease when claim:true, surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
5252
+ description: "Agent label recorded on the lease when the tab is claimed; surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
5253
5253
  },
5254
5254
  ttlMs: {
5255
5255
  type: "number",
5256
- description: "How long the lease taken by claim:true survives without use before it is reclaimable. Defaults to CDP_LEASE_TTL_MS, else 900000 (15 minutes). Every checked call refreshes it, so an active agent never expires. Ignored without claim:true."
5256
+ description: "How long a claimed lease survives without use before it is reclaimable. Defaults to CDP_LEASE_TTL_MS, else 900000 (15min); every checked call refreshes it. Ignored when not claiming."
5257
5257
  }
5258
5258
  },
5259
5259
  required: [],
@@ -5262,17 +5262,17 @@ var MANIFEST = [
5262
5262
  },
5263
5263
  {
5264
5264
  name: "close_page",
5265
- description: "Close a page target via Target.closeTarget. Requires an explicit, resolvable target; refuses to guess and errors if the selector matches no target.",
5265
+ description: "Close a page target via Target.closeTarget. Requires an explicit, resolvable target; errors rather than guessing when the selector matches nothing.",
5266
5266
  inputSchema: {
5267
5267
  type: "object",
5268
5268
  properties: {
5269
5269
  lease: {
5270
5270
  type: "string",
5271
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5271
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5272
5272
  },
5273
5273
  target: {
5274
5274
  type: "string",
5275
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5275
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5276
5276
  }
5277
5277
  },
5278
5278
  required: [
@@ -5289,11 +5289,11 @@ var MANIFEST = [
5289
5289
  properties: {
5290
5290
  lease: {
5291
5291
  type: "string",
5292
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5292
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5293
5293
  },
5294
5294
  target: {
5295
5295
  type: "string",
5296
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5296
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5297
5297
  }
5298
5298
  },
5299
5299
  required: [
@@ -5310,11 +5310,11 @@ var MANIFEST = [
5310
5310
  properties: {
5311
5311
  lease: {
5312
5312
  type: "string",
5313
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5313
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5314
5314
  },
5315
5315
  target: {
5316
5316
  type: "string",
5317
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5317
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5318
5318
  },
5319
5319
  url: {
5320
5320
  type: "string",
@@ -5330,7 +5330,7 @@ var MANIFEST = [
5330
5330
  "back",
5331
5331
  "forward"
5332
5332
  ],
5333
- description: "Traverse this tab's session history instead of loading a url: 'back' and 'forward' are the browser's Back and Forward buttons. Mutually exclusive with 'url' and 'reload' — passing two of the three is refused by name rather than resolved by precedence. Works on BOTH backends (Chrome: Page.getNavigationHistory + Page.navigateToHistoryEntry; Firefox: browsingContext.traverseHistory) and waits for the same load milestone as an ordinary navigation. Going back from the first entry, or forward from the last, is an ERROR naming the direction — never a silent success that navigated nowhere. The result carries traversed:'back'|'forward' alongside the resulting url."
5333
+ description: "Traverse this tab's session history instead of loading a url: 'back' and 'forward' are the browser's Back and Forward buttons. Mutually exclusive with 'url' and 'reload' — passing two of the three is refused by name, not resolved by precedence. Works on BOTH backends and waits for the same load milestone as an ordinary navigation. Going back from the first entry, or forward from the last, is an ERROR naming the direction — never a silent no-op. The result carries traversed:'back'|'forward' alongside the resulting url."
5334
5334
  },
5335
5335
  ignoreCache: {
5336
5336
  type: "boolean",
@@ -5354,17 +5354,17 @@ var MANIFEST = [
5354
5354
  },
5355
5355
  {
5356
5356
  name: "wait_for",
5357
- description: "Poll a target page until the given substring appears in document.body.innerText (Runtime.evaluate on a fixed interval), or throw on timeout. Text-substring waiting only: no aria/role/selector or event variants; throws rather than returning {found:false}.",
5357
+ description: "Poll the target until the substring appears in document.body.innerText (Runtime.evaluate on a fixed interval), or throw on timeout. Text-substring only -- no aria/role/selector or event variants; throws rather than returning {found:false}.",
5358
5358
  inputSchema: {
5359
5359
  type: "object",
5360
5360
  properties: {
5361
5361
  lease: {
5362
5362
  type: "string",
5363
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5363
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5364
5364
  },
5365
5365
  target: {
5366
5366
  type: "string",
5367
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5367
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5368
5368
  },
5369
5369
  text: {
5370
5370
  type: "string",
@@ -5372,11 +5372,11 @@ var MANIFEST = [
5372
5372
  },
5373
5373
  timeoutMs: {
5374
5374
  type: "number",
5375
- description: "Total time budget in milliseconds. Defaults to 15000."
5375
+ description: "Total time budget in ms. Default 15000."
5376
5376
  },
5377
5377
  pollMs: {
5378
5378
  type: "number",
5379
- description: "Poll interval in milliseconds. Defaults to 250."
5379
+ description: "Poll interval in ms. Default 250."
5380
5380
  }
5381
5381
  },
5382
5382
  required: [
@@ -5387,37 +5387,37 @@ var MANIFEST = [
5387
5387
  },
5388
5388
  {
5389
5389
  name: "evaluate_script",
5390
- description: "Run arbitrary JavaScript in the target page's main-world context over raw CDP and return the evaluated value (returnByValue). With no 'args' the 'expression' is evaluated as a raw expression; when 'args' is provided 'expression' must be a function literal (arrow or classic) invoked on globalThis with the args passed positionally. A thrown exception surfaces as an error; non-serializable returns (DOM nodes, functions) come back as their CDP description string. Pass 'savePath' to write the evaluated value to a JSON file instead: the response then carries only {path,bytes,type,target} and the value itself never appears in it, in any form. That is the way to read a credential (a JWT or session token out of localStorage, for example) without putting the secret into the caller's transcript. This is the one tool that can EVALUATE inside an MV3 extension's background SERVICE WORKER rather than a page (list_network_requests/get_network_request/list_console_messages OBSERVE one): pass target 'worker:<extension-id>' (Chrome only) and read chrome.storage.local, chrome.runtime and the worker's own globals directly, with an idle-evicted worker started for you first (see 'wake').",
5390
+ description: "Run arbitrary JavaScript in the target's main-world context over raw CDP and return the evaluated value (returnByValue). With no 'args', 'expression' is a raw expression; with 'args' it must be a function literal (arrow or classic) invoked on globalThis with args passed positionally. A thrown exception surfaces as an error; non-serializable returns (DOM nodes, functions) come back as their CDP description string. savePath writes the value to a JSON file instead -- the response then carries only {path,bytes,type,target} and the value never appears in it, the way to read a credential (a JWT/session token from localStorage) without putting the secret in the transcript. This is the one tool that EVALUATES inside an MV3 extension's background SERVICE WORKER rather than OBSERVING it (Chrome, Capability 'worker.targets'): a worker: target reads chrome.storage.local, chrome.runtime and the worker's own globals directly, with an idle-evicted worker started first (see 'wake').",
5391
5391
  inputSchema: {
5392
5392
  type: "object",
5393
5393
  properties: {
5394
5394
  lease: {
5395
5395
  type: "string",
5396
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5396
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5397
5397
  },
5398
5398
  target: {
5399
5399
  type: "string",
5400
- description: "Page selector: 'active' (or omit) -> first page-type target | '<32-hex targetId>' -> exact target by id | 'index:N' -> Nth page-type target (0-based) | 'url:<substring>' -> first page whose url contains substring | 'title:<substring>' -> first page whose title contains substring | 'label:<name>' -> the page-type target with exactly this label (checked against the origin ledger and live leases) | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js) and read chrome.storage.local from it. The worker: arm is CHROME-ONLY (Capability 'worker.targets') and is accepted by four tools only — evaluate_script, list_network_requests, get_network_request and list_console_messages — never as universal grammar; an MV3 worker that has been idle-evicted is started automatically first, see 'wake'."
5400
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
5401
5401
  },
5402
5402
  wake: {
5403
5403
  type: "boolean",
5404
- description: "Only valid with a 'worker:<substring>' target, and rejected on a page target rather than ignored. Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so a wake asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything; the error then says the worker may simply be asleep."
5404
+ description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
5405
5405
  },
5406
5406
  expression: {
5407
5407
  type: "string",
5408
- description: "JavaScript to run. Evaluated as an expression when 'args' is omitted; must be a function literal (arrow or classic) whose parameters receive 'args' when 'args' is provided. REQUIRED — the code always goes in this key, never in 'function', 'code', 'js', 'script', 'fn' or 'body'; a call using one of those instead is rejected with an error naming the wrong key."
5408
+ description: "JavaScript to run. Evaluated as an expression when 'args' is omitted; must be a function literal (arrow or classic) whose parameters receive 'args' when provided. REQUIRED -- the code always goes in this key, never in 'function', 'code', 'js', 'script', 'fn' or 'body'; a call using one of those is rejected with an error naming the wrong key."
5409
5409
  },
5410
5410
  awaitPromise: {
5411
5411
  type: "boolean",
5412
- description: "Await the result if it is a Promise (default true)."
5412
+ description: "Await the result if it is a Promise. Default true."
5413
5413
  },
5414
5414
  args: {
5415
5415
  type: "array",
5416
- description: "Positional JSON-serializable arguments to pass to the expression, treating it as a function. No live element/page handle is bound."
5416
+ description: "Positional JSON-serializable arguments passed to the expression (treated as a function). No live element/page handle is bound."
5417
5417
  },
5418
5418
  savePath: {
5419
5419
  type: "string",
5420
- description: "Write the evaluated value to this file as JSON and KEEP IT OUT OF THE RESPONSE: with savePath set the result is {path,bytes,type,target} only, with no copy, preview or truncation of the value. Use it to read credentials without putting them in a transcript. An absolute path (starting with /) is used as-is; a relative path is resolved under the artifact dir (/tmp/cdp-toolkit). Missing parent directories are created. Omit it to get the value back inline, exactly as before."
5420
+ description: "Write the evaluated value to this file as JSON and KEEP IT OUT OF THE RESPONSE: the result is {path,bytes,type,target} only, with no copy, preview or truncation of the value -- read credentials without putting them in a transcript. An absolute path (starting with /) is used as-is; a relative path resolves under the artifact dir (/tmp/cdp-toolkit); missing parents are created. Omit to get the value back inline."
5421
5421
  }
5422
5422
  },
5423
5423
  required: [
@@ -5428,21 +5428,21 @@ var MANIFEST = [
5428
5428
  },
5429
5429
  {
5430
5430
  name: "list_cookies",
5431
- description: "Read the cookie store for the target page, INCLUDING httpOnly cookies, which document.cookie cannot see and an evaluate_script call therefore cannot reach. Each cookie carries name, value, domain, path, expires (Unix seconds, -1 for a session cookie), size, httpOnly, secure, sameSite ('strict'|'lax'|'none'|'default') and session. The read is page-scoped, not browser-wide: it returns the cookies of the resolved tab (Chrome: Network.getCookies; Firefox: storage.getCookies partitioned by that browsing context), so point at a page on the site whose cookies you want. Filter with 'domain' and/or 'name'. Pass 'savePath' to write the cookie array to a JSON file instead: the response is then {path,bytes,count,target} only, with no cookie value in it in any form, which is how to capture a session cookie without putting the credential in the caller's transcript.",
5431
+ description: "Read the target page's cookie store INCLUDING httpOnly cookies, which document.cookie (and therefore evaluate_script) cannot see. Page-scoped, not browser-wide: returns cookies of the resolved tab, so point at a page on the site you want. Each cookie carries name, value, domain, path, expires (Unix seconds, -1 for a session cookie), size, httpOnly, secure, sameSite ('strict'|'lax'|'none'|'default'), session. Filter with 'domain' and/or 'name'. Pass 'savePath' to write the array to a JSON file instead: the response is then {path,bytes,count,target} with no cookie value in any form, the way to capture a session cookie without putting the credential in the transcript.",
5432
5432
  inputSchema: {
5433
5433
  type: "object",
5434
5434
  properties: {
5435
5435
  lease: {
5436
5436
  type: "string",
5437
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5437
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5438
5438
  },
5439
5439
  target: {
5440
5440
  type: "string",
5441
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5441
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5442
5442
  },
5443
5443
  domain: {
5444
5444
  type: "string",
5445
- description: "Keep only cookies for this domain. A leading dot is ignored on both sides and subdomains of the given domain match too, so 'example.test' matches '.example.test' and 'app.example.test'. No wildcards."
5445
+ description: "Keep only cookies for this domain. A leading dot is ignored on both sides and subdomains match too, so 'example.test' matches '.example.test' and 'app.example.test'. No wildcards."
5446
5446
  },
5447
5447
  name: {
5448
5448
  type: "string",
@@ -5450,7 +5450,7 @@ var MANIFEST = [
5450
5450
  },
5451
5451
  savePath: {
5452
5452
  type: "string",
5453
- description: "Write the cookie array to this file as JSON and KEEP THE VALUES OUT OF THE RESPONSE: with savePath set the result is {path,bytes,count,target} only, with no copy, preview or truncation of any cookie value. An absolute path (starting with /) is used as-is; a relative path is resolved under the artifact dir (/tmp/cdp-toolkit). Missing parent directories are created. Omit it to get the cookies back inline."
5453
+ description: "Write the cookie array here as JSON and KEEP THE VALUES OUT OF THE RESPONSE: the result is {path,bytes,count,target} only, with no preview or truncation of any value. An absolute path (starting with /) is used as-is; a relative path resolves under the artifact dir (/tmp/cdp-toolkit); missing parents are created. Omit to get the cookies back inline."
5454
5454
  }
5455
5455
  },
5456
5456
  required: [],
@@ -5459,17 +5459,17 @@ var MANIFEST = [
5459
5459
  },
5460
5460
  {
5461
5461
  name: "set_cookie",
5462
- description: "Write one cookie into the target page's cookie store, INCLUDING an httpOnly or secure cookie, which document.cookie cannot create and an evaluate_script call therefore cannot either. Chrome uses Network.setCookie, Firefox uses storage.setCookie partitioned by the resolved browsing context. Either 'url' or 'domain' is REQUIRED: a cookie has to be attributed to a site and the call is refused with an error when neither is given, rather than guessing the current page's origin. The response is {set:true,target} and never echoes the value back, so a credential you just supplied does not land in the transcript twice. A 'set:true' is earned, not assumed: Chrome answers Network.setCookie with success:false when it declines a cookie (a domain the url does not belong to, a secure cookie on an insecure origin, an oversized value) and that refusal is raised as an error. On Firefox, 'domain' is derived from 'url' when only the url was given, because BiDi has no url parameter; a url with no host, such as about:blank or a data URL, is an error rather than a silent no-op. Read the result back with list_cookies when you need proof.",
5462
+ description: "Write one cookie into the target page's cookie store, INCLUDING httpOnly or secure cookies that document.cookie (and therefore evaluate_script) cannot create. Either 'url' or 'domain' is REQUIRED: the call is refused when neither is given, rather than guessing the page origin. The response is {set:true,target} and never echoes the value back. A 'set:true' is earned, not assumed: Chrome raises an error when it declines the cookie (a domain the url does not belong to, a secure cookie on an insecure origin, an oversized value). On Firefox 'domain' is derived from 'url' when only the url was given (BiDi has no url parameter), and a url with no host such as about:blank or a data URL is an error, not a silent no-op. Read the result back with list_cookies when you need proof.",
5463
5463
  inputSchema: {
5464
5464
  type: "object",
5465
5465
  properties: {
5466
5466
  lease: {
5467
5467
  type: "string",
5468
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5468
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5469
5469
  },
5470
5470
  target: {
5471
5471
  type: "string",
5472
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5472
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5473
5473
  },
5474
5474
  name: {
5475
5475
  type: "string",
@@ -5481,19 +5481,19 @@ var MANIFEST = [
5481
5481
  },
5482
5482
  url: {
5483
5483
  type: "string",
5484
- description: "The URL the cookie is set for, for example 'https://example.com/'. Chrome derives domain, path and secure from it. Firefox derives only the domain from its host. Give this or 'domain'."
5484
+ description: "URL the cookie is set for, e.g. 'https://example.com/'. Chrome derives domain, path and secure from it; Firefox derives only the domain from its host. Give this or 'domain'."
5485
5485
  },
5486
5486
  domain: {
5487
5487
  type: "string",
5488
- description: "Cookie domain, for example 'example.com' or '.example.com' for subdomains. Give this or 'url'. When both are given, this wins on Firefox and Chrome applies its own url plus domain consistency rule."
5488
+ description: "Cookie domain, e.g. 'example.com' or '.example.com' for subdomains. Give this or 'url'. When both are given, this wins on Firefox and Chrome applies its own url plus domain consistency rule."
5489
5489
  },
5490
5490
  path: {
5491
5491
  type: "string",
5492
- description: "Cookie path. Passed through exactly as given and NOT defaulted to '/': with a 'url' Chrome derives the path from it, so an invented default here would widen a cookie you meant to scope narrowly."
5492
+ description: "Cookie path, passed through exactly as given and NOT defaulted to '/': with a 'url' Chrome derives the path from it, so an invented default here would widen a cookie you meant to scope narrowly."
5493
5493
  },
5494
5494
  expires: {
5495
5495
  type: "number",
5496
- description: "Expiry as Unix time in SECONDS, matching what list_cookies reports. Omit it for a session cookie, which is what both backends do when no expiry is supplied."
5496
+ description: "Expiry as Unix time in SECONDS, matching what list_cookies reports. Omit for a session cookie, which is what both backends do when no expiry is supplied."
5497
5497
  },
5498
5498
  httpOnly: {
5499
5499
  type: "boolean",
@@ -5505,27 +5505,35 @@ var MANIFEST = [
5505
5505
  },
5506
5506
  sameSite: {
5507
5507
  type: "string",
5508
- enum: ["strict", "lax", "none", "default"],
5508
+ enum: [
5509
+ "strict",
5510
+ "lax",
5511
+ "none",
5512
+ "default"
5513
+ ],
5509
5514
  description: "SameSite attribute, lowercase, matching what list_cookies reports. 'default' means the attribute is not set at all."
5510
5515
  }
5511
5516
  },
5512
- required: ["name", "value"],
5517
+ required: [
5518
+ "name",
5519
+ "value"
5520
+ ],
5513
5521
  additionalProperties: false
5514
5522
  }
5515
5523
  },
5516
5524
  {
5517
5525
  name: "delete_cookies",
5518
- description: "Delete the named cookie from the target page's cookie store, httpOnly cookies included. Chrome uses Network.deleteCookies, Firefox uses storage.deleteCookies with a filter, partitioned by the resolved browsing context. Both 'name' and one of 'url' or 'domain' are REQUIRED: without a site constraint the call would delete by name across the whole partition, so it is refused with an error instead. Narrow further with 'path'. The response is {deleted:true,target} and carries NO count, because neither protocol reports how many cookies it removed and a number here would be invented; call list_cookies before and after when you need a real count. 'deleted:true' means the backend accepted and performed the deletion, not that a matching cookie existed, since deleting an absent cookie is a success on both backends.",
5526
+ description: "Delete the named cookie from the target page's cookie store, httpOnly cookies included. Both 'name' and one of 'url' or 'domain' are REQUIRED: without a site constraint it would delete by name across the whole partition, so it is refused with an error instead. Narrow further with 'path'. The response is {deleted:true,target} and carries NO count, because neither protocol reports how many cookies it removed and a number here would be invented; call list_cookies before and after for a real count. 'deleted:true' means the backend accepted and performed the deletion, not that a matching cookie existed, since deleting an absent cookie succeeds on both backends.",
5519
5527
  inputSchema: {
5520
5528
  type: "object",
5521
5529
  properties: {
5522
5530
  lease: {
5523
5531
  type: "string",
5524
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5532
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5525
5533
  },
5526
5534
  target: {
5527
5535
  type: "string",
5528
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5536
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5529
5537
  },
5530
5538
  name: {
5531
5539
  type: "string",
@@ -5533,7 +5541,7 @@ var MANIFEST = [
5533
5541
  },
5534
5542
  url: {
5535
5543
  type: "string",
5536
- description: "Delete cookies matching this URL, for example 'https://example.com/'. Give this or 'domain'. On Firefox only the host is used, because BiDi's filter has no url field."
5544
+ description: "Delete cookies matching this URL, e.g. 'https://example.com/'. Give this or 'domain'. On Firefox only the host is used, because BiDi's filter has no url field."
5537
5545
  },
5538
5546
  domain: {
5539
5547
  type: "string",
@@ -5544,27 +5552,29 @@ var MANIFEST = [
5544
5552
  description: "Narrow the deletion to cookies with this exact path. Omit to match any path allowed by the other constraints."
5545
5553
  }
5546
5554
  },
5547
- required: ["name"],
5555
+ required: [
5556
+ "name"
5557
+ ],
5548
5558
  additionalProperties: false
5549
5559
  }
5550
5560
  },
5551
5561
  {
5552
5562
  name: "take_snapshot",
5553
- description: "Capture the page's accessibility tree (Accessibility.getFullAXTree) as a compact indented text tree where each line is prefixed with [uid], the node's CDP backendDOMNodeId. These uids are the stateless element references that every interaction tool (click/hover/fill/etc.) feeds back to resolve a live DOM node (via DOM.resolveNode({ backendNodeId: uid })), so run this first to discover uids.",
5563
+ description: "Captures the page's accessibility tree as an indented text tree, each line prefixed [uid] (a CDP backendDOMNodeId). Every interaction tool (click/hover/fill/etc.) resolves that uid via DOM.resolveNode — run this first to discover uids to act on.",
5554
5564
  inputSchema: {
5555
5565
  type: "object",
5556
5566
  properties: {
5557
5567
  lease: {
5558
5568
  type: "string",
5559
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5569
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5560
5570
  },
5561
5571
  target: {
5562
5572
  type: "string",
5563
- description: "Target page selector: 'active' (default, = first page-type target) | 'index:N' (Nth page-type target, 0-based) | 'url:<substring>' | 'title:<substring>' | 'label:<name>' (exact label match, origin ledger or live lease) | '<32-hex targetId>' (exact target by id)."
5573
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5564
5574
  },
5565
5575
  interactiveOnly: {
5566
5576
  type: "boolean",
5567
- description: "When true, emit only interactive/meaningful nodes flattened into a readable list; default false returns the full hierarchical a11y tree."
5577
+ description: "When true, emit only interactive/meaningful nodes as a flat list; default false returns the full hierarchical tree."
5568
5578
  }
5569
5579
  },
5570
5580
  required: [],
@@ -5573,25 +5583,25 @@ var MANIFEST = [
5573
5583
  },
5574
5584
  {
5575
5585
  name: "click",
5576
- description: "Click an element via a synthetic mouse press/release at the element's scrolled-into-view bounding-rect center. Target the element with exactly one of 'uid' (a CDP backendDOMNodeId from take_snapshot) or 'selector' (a CSS selector). clickCount:3 triple-clicks (selects a paragraph/line in most editors). 'modifiers' holds Alt/Control/Meta/Shift for the press and release, like a real modifier-click; on the Firefox backend a non-empty 'modifiers' throws (not yet supported over BiDi), so a modifier click there needs --browser chrome.",
5586
+ description: "Click an element via a synthetic mouse press/release at its scrolled-into-view center. Give exactly one of uid or selector. modifiers (Alt/Control/Meta/Shift) throws on the Firefox backend — use --browser chrome for a modifier-click. clickCount:3 triple-clicks (selects a line/paragraph in most editors).",
5577
5587
  inputSchema: {
5578
5588
  type: "object",
5579
5589
  properties: {
5580
5590
  lease: {
5581
5591
  type: "string",
5582
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5592
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5583
5593
  },
5584
5594
  target: {
5585
5595
  type: "string",
5586
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5596
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5587
5597
  },
5588
5598
  uid: {
5589
5599
  type: "number",
5590
- description: "CDP backendDOMNodeId of the element to click, obtained from take_snapshot. Provide exactly one of uid or selector."
5600
+ description: "CDP backendDOMNodeId of the element to click, from take_snapshot. Exactly one of uid or selector."
5591
5601
  },
5592
5602
  selector: {
5593
5603
  type: "string",
5594
- description: "CSS selector for the element to click (resolved via document.querySelector). Provide exactly one of uid or selector."
5604
+ description: "CSS selector for the element to click (document.querySelector). Exactly one of uid or selector."
5595
5605
  },
5596
5606
  button: {
5597
5607
  type: "string",
@@ -5604,7 +5614,7 @@ var MANIFEST = [
5604
5614
  },
5605
5615
  clickCount: {
5606
5616
  type: "number",
5607
- description: "Number of clicks: 1 = single (default), 2 = double-click, 3 = triple-click."
5617
+ description: "Number of clicks: 1 (default), 2 = double-click, 3 = triple-click."
5608
5618
  },
5609
5619
  modifiers: {
5610
5620
  type: "array",
@@ -5617,7 +5627,7 @@ var MANIFEST = [
5617
5627
  "Shift"
5618
5628
  ]
5619
5629
  },
5620
- description: "Modifier keys held for the click's press and release, e.g. ['Shift'] for a shift-click. Not supported on the Firefox backend: passing a non-empty array there throws."
5630
+ description: "Modifier keys held for the click's press and release, e.g. ['Shift']. Non-empty on the Firefox backend throws — not supported over BiDi."
5621
5631
  }
5622
5632
  },
5623
5633
  required: [],
@@ -5626,25 +5636,25 @@ var MANIFEST = [
5626
5636
  },
5627
5637
  {
5628
5638
  name: "hover",
5629
- description: "Hover the mouse over an element by dispatching a mouseMoved event at its scrolled-into-view center, firing framework hover handlers. Target with exactly one of 'uid' (a CDP backendDOMNodeId from take_snapshot) or 'selector' (a CSS selector).",
5639
+ description: "Hovers the mouse over an element by dispatching a mouseMoved event at its scrolled-into-view center, firing framework hover handlers. Give exactly one of uid or selector.",
5630
5640
  inputSchema: {
5631
5641
  type: "object",
5632
5642
  properties: {
5633
5643
  lease: {
5634
5644
  type: "string",
5635
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5645
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5636
5646
  },
5637
5647
  target: {
5638
5648
  type: "string",
5639
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5649
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5640
5650
  },
5641
5651
  uid: {
5642
5652
  type: "number",
5643
- description: "CDP backendDOMNodeId of the element to hover, obtained from take_snapshot. Provide exactly one of uid or selector."
5653
+ description: "CDP backendDOMNodeId of the element to hover, from take_snapshot. Exactly one of uid or selector."
5644
5654
  },
5645
5655
  selector: {
5646
5656
  type: "string",
5647
- description: "CSS selector for the element to hover (resolved via document.querySelector). Provide exactly one of uid or selector."
5657
+ description: "CSS selector for the element to hover (document.querySelector). Exactly one of uid or selector."
5648
5658
  }
5649
5659
  },
5650
5660
  required: [],
@@ -5653,21 +5663,21 @@ var MANIFEST = [
5653
5663
  },
5654
5664
  {
5655
5665
  name: "drag",
5656
- description: `Drag from a source element to a destination. 'from' takes exactly one of uid (a CDP backendDOMNodeId from take_snapshot) or selector. The destination is exactly one of 'to' (an element via uid/selector, or an absolute viewport point via x+y) or 'by' ({dx,dy} offset from the source point — sliders, map panning, resize handles). mode:'mouse' (default) dispatches synthetic mouse press/move/release: right for widgets built on raw pointer events, and Chrome does turn it into a real drag, but WHICH drag events reach the page depends on where the interpolated pointer path happens to land — measured on Chrome 151, the default steps:2 delivers ZERO dragover events, so an HTML5 drop zone written the standard way (preventDefault inside dragover) refuses the drop entirely. mode:'html5' performs the drag deterministically instead: Chrome's drag interception hands back the DragData the page's own dragstart built, and the toolkit replays it as dragEnter/dragOver/drop exactly at the destination, so a draggable="true" / dataTransfer drop zone works regardless of the pointer path. mode:'html5' is CHROME-ONLY: it requires capability 'input.html5Drag' and is rejected with a clear error under the Firefox backend, where the tool itself remains available for mouse-mode drags. 'steps' (default 2) sets how many interpolated mouse-move events are dispatched between source and destination; raise it for DnD libraries with a movement threshold or per-frame sampling.`,
5666
+ description: "Drags from a source element to a destination or by an offset. mode:'mouse' (default) dispatches synthetic mouse press/move/release; at the default steps:2 an HTML5 drop zone (dragover preventDefault) gets ZERO dragover events and refuses the drop. mode:'html5' replays real dragstart/dragEnter/dragOver/drop with the page's own dataTransfer instead — deterministic regardless of pointer path, but CHROME-ONLY (rejected with a clear error on the Firefox backend). 'from' takes uid or selector; destination is exactly one of 'to' (uid/selector/x+y) or 'by' ({dx,dy} offset — sliders, map panning, resize handles).",
5657
5667
  inputSchema: {
5658
5668
  type: "object",
5659
5669
  properties: {
5660
5670
  lease: {
5661
5671
  type: "string",
5662
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5672
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5663
5673
  },
5664
5674
  target: {
5665
5675
  type: "string",
5666
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5676
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5667
5677
  },
5668
5678
  from: {
5669
5679
  type: "object",
5670
- description: "Source element to drag from. Provide exactly one of uid or selector.",
5680
+ description: "Source element to drag from. Exactly one of uid or selector.",
5671
5681
  properties: {
5672
5682
  uid: {
5673
5683
  type: "number",
@@ -5682,7 +5692,7 @@ var MANIFEST = [
5682
5692
  },
5683
5693
  to: {
5684
5694
  type: "object",
5685
- description: "Where the drag ends: exactly one of uid, selector, or x+y. Mutually exclusive with 'by'; exactly one of to/by is required.",
5695
+ description: "Where the drag ends: exactly one of uid, selector, or x+y. Mutually exclusive with 'by'; exactly one of to/by required.",
5686
5696
  properties: {
5687
5697
  uid: {
5688
5698
  type: "number",
@@ -5705,7 +5715,7 @@ var MANIFEST = [
5705
5715
  },
5706
5716
  by: {
5707
5717
  type: "object",
5708
- description: "Drag by an offset from the source element's center instead of to a destination: at least one of dx/dy, the other defaults to 0. Mutually exclusive with 'to'; exactly one of to/by is required. Use for sliders (by:{dx:40}), map panning, and resize handles.",
5718
+ description: "Offset from the source element's center: at least one of dx/dy (other defaults to 0). Mutually exclusive with 'to'; exactly one of to/by required. For sliders (by:{dx:40}), map panning, resize handles.",
5709
5719
  properties: {
5710
5720
  dx: {
5711
5721
  type: "number",
@@ -5724,11 +5734,11 @@ var MANIFEST = [
5724
5734
  "mouse",
5725
5735
  "html5"
5726
5736
  ],
5727
- description: "'mouse' (default): synthetic mouse press/move/release, right for widgets built on raw pointer events. Chrome does turn this into a real HTML5 drag too, but WHICH drag events reach the page depends on the interpolated pointer path: at the default steps:2, an HTML5 drop zone written the standard way (preventDefault inside dragover) sees zero dragover events and refuses the drop. 'html5': real HTML5 drag-and-drop (dragstart/dragEnter/dragOver/drop with the page's own dataTransfer), deterministic regardless of pointer path. 'html5' is CHROME-ONLY and is rejected with a clear error under the Firefox backend."
5737
+ description: "'mouse' (default): synthetic mouse press/move/release — at steps:2 an HTML5 drop zone sees zero dragover events and refuses the drop. 'html5': real HTML5 drag-and-drop, deterministic regardless of pointer path, CHROME-ONLY (rejected on Firefox)."
5728
5738
  },
5729
5739
  steps: {
5730
5740
  type: "number",
5731
- description: "Number of interpolated mouse-move events dispatched between the source and destination points, evenly spaced, the last landing exactly on the destination. Integer 1-500, default 2 (midpoint then destination). Raise it for DnD libraries with a movement threshold or per-frame sampling."
5741
+ description: "Number of interpolated mouse-move events between source and destination, evenly spaced, last landing on the destination. Integer 1-500, default 2. Raise for DnD libraries with a movement threshold or per-frame sampling."
5732
5742
  }
5733
5743
  },
5734
5744
  required: [
@@ -5739,41 +5749,41 @@ var MANIFEST = [
5739
5749
  },
5740
5750
  {
5741
5751
  name: "scroll",
5742
- description: "Dispatch a wheel/scroll event at an anchor point: provide at most one of 'uid', 'selector', or 'x'+'y'; omit all three to scroll at the viewport center. An element anchor ('uid' or 'selector') is scrolled into view first, the same as click/hover. At least one of 'deltaX'/'deltaY' is required; positive 'deltaY' scrolls DOWN and positive 'deltaX' scrolls RIGHT (wheel-event convention). Chrome dispatches Input.dispatchMouseEvent{type:'mouseWheel'}; Firefox dispatches WebDriver BiDi's 'wheel' input source. Returns the resolved anchor point ({x,y}) plus the delta actually dispatched.",
5752
+ description: "Dispatches a wheel/scroll event at an anchor point — element (uid/selector) or x+y, scrolled into view first if an element; omit all three for viewport center. At least one of deltaX/deltaY required: positive deltaY scrolls DOWN, positive deltaX scrolls RIGHT (wheel-event convention — easy to invert). Returns the resolved anchor {x,y} plus the delta actually dispatched.",
5743
5753
  inputSchema: {
5744
5754
  type: "object",
5745
5755
  properties: {
5746
5756
  lease: {
5747
5757
  type: "string",
5748
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5758
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5749
5759
  },
5750
5760
  target: {
5751
5761
  type: "string",
5752
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5762
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5753
5763
  },
5754
5764
  uid: {
5755
5765
  type: "number",
5756
- description: "CDP backendDOMNodeId of the element to scroll into view and anchor at, obtained from take_snapshot. Provide at most one of uid, selector, or x+y; omit all three to scroll at the viewport center."
5766
+ description: "CDP backendDOMNodeId of the element to scroll into view and anchor at, from take_snapshot. At most one of uid, selector, or x+y."
5757
5767
  },
5758
5768
  selector: {
5759
5769
  type: "string",
5760
- description: "CSS selector for the element to scroll into view and anchor at (resolved via document.querySelector). Provide at most one of uid, selector, or x+y; omit all three to scroll at the viewport center."
5770
+ description: "CSS selector for the element to anchor at (document.querySelector). At most one of uid, selector, or x+y."
5761
5771
  },
5762
5772
  x: {
5763
5773
  type: "number",
5764
- description: "Absolute viewport x-coordinate to anchor the scroll at. Must be given together with 'y'. Provide at most one of uid, selector, or x+y."
5774
+ description: "Absolute viewport x-coordinate to anchor at; must be given with y. At most one of uid, selector, or x+y."
5765
5775
  },
5766
5776
  y: {
5767
5777
  type: "number",
5768
- description: "Absolute viewport y-coordinate to anchor the scroll at. Must be given together with 'x'. Provide at most one of uid, selector, or x+y."
5778
+ description: "Absolute viewport y-coordinate to anchor at; must be given with x. At most one of uid, selector, or x+y."
5769
5779
  },
5770
5780
  deltaX: {
5771
5781
  type: "number",
5772
- description: "Horizontal scroll delta; positive scrolls RIGHT. At least one of deltaX/deltaY is required."
5782
+ description: "Horizontal scroll delta; positive scrolls RIGHT. At least one of deltaX/deltaY required."
5773
5783
  },
5774
5784
  deltaY: {
5775
5785
  type: "number",
5776
- description: "Vertical scroll delta; positive scrolls DOWN. At least one of deltaX/deltaY is required."
5786
+ description: "Vertical scroll delta; positive scrolls DOWN. At least one of deltaX/deltaY required."
5777
5787
  }
5778
5788
  },
5779
5789
  required: [],
@@ -5782,17 +5792,17 @@ var MANIFEST = [
5782
5792
  },
5783
5793
  {
5784
5794
  name: "dispatch_mouse",
5785
- description: "Dispatch exactly one raw mouse event ('move', 'down', or 'up') at absolute viewport coordinates: the toolkit's lowest-level input primitive. Compose move/down/move/up calls yourself to reach anything a physical mouse can do that click/drag's fixed sequences cannot — canvas drag-painting, marquee/rubber-band selection, a custom widget with its own hit-testing. Chrome-only (capability 'input.raw'): absent from tools/list under the Firefox backend, never present-and-throwing.",
5795
+ description: "Dispatches one raw mouse event (move/down/up) at absolute viewport coordinates — the toolkit's lowest-level input primitive. Compose move/down/move/up calls yourself for anything click/drag's fixed sequences can't reach: canvas drag-painting, marquee/rubber-band selection, custom hit-testing widgets. Chrome-only: absent from tools/list under the Firefox backend, never present-and-throwing.",
5786
5796
  inputSchema: {
5787
5797
  type: "object",
5788
5798
  properties: {
5789
5799
  lease: {
5790
5800
  type: "string",
5791
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5801
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5792
5802
  },
5793
5803
  target: {
5794
5804
  type: "string",
5795
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5805
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5796
5806
  },
5797
5807
  action: {
5798
5808
  type: "string",
@@ -5805,11 +5815,11 @@ var MANIFEST = [
5805
5815
  },
5806
5816
  x: {
5807
5817
  type: "number",
5808
- description: "Viewport x-coordinate. Required on every call: CDP has no notion of a 'current pointer position' to default from."
5818
+ description: "Viewport x-coordinate. Required every call — CDP has no 'current pointer position' to default from."
5809
5819
  },
5810
5820
  y: {
5811
5821
  type: "number",
5812
- description: "Viewport y-coordinate. Required on every call."
5822
+ description: "Viewport y-coordinate. Required every call."
5813
5823
  },
5814
5824
  button: {
5815
5825
  type: "string",
@@ -5822,7 +5832,7 @@ var MANIFEST = [
5822
5832
  },
5823
5833
  clickCount: {
5824
5834
  type: "number",
5825
- description: "Click-run length for a 'down' or 'up' event (2 = the second half of a double-click, 3 = triple-click). Ignored for 'move'; defaults to 1."
5835
+ description: "Click-run length for a 'down' or 'up' event (2 = second half of a double-click, 3 = triple-click). Ignored for 'move'; defaults to 1."
5826
5836
  },
5827
5837
  modifiers: {
5828
5838
  type: "array",
@@ -5848,17 +5858,17 @@ var MANIFEST = [
5848
5858
  },
5849
5859
  {
5850
5860
  name: "wait_for_download",
5851
- description: "Wait for a file download to finish and return it as a real file on disk: {path,suggestedFilename,bytes,url,target}. The file is written under the artifact dir's downloads/ folder and renamed from Chrome's internal guid to the page's own filename, collision-suffixed (report.csv, report-1.csv, ...). ORDERING RULE, and it is not optional: download capture must be ARMED BEFORE the click that starts the download — call wait_for_download{arm:true} first (it arms and returns immediately, reporting {armed:true,downloadPath,pending}), then click, then call wait_for_download to collect the finished file. This is Chrome's behavior, not a preference: the download-behavior override is per-connection state that Chrome REVERTS the moment the arming client disconnects, and an unarmed headless Chrome denies the download outright, so a download triggered before anything armed is lost with no file anywhere. SIDE EFFECT, browser-global: arming redirects EVERY download in this browser (all tabs, all origins) into the toolkit's downloads directory for as long as this server runs, and downloads no longer land in the user's normal Downloads folder. Because the arm lives on a connection this server process holds open, this is an MCP-server capability: under the one-shot CLI the connection dies with the process and nothing is captured. Chrome-only (capability 'browser.downloads'): absent from tools/list under the Firefox backend, never present-and-throwing, because WebDriver BiDi has no command to redirect a download to a chosen directory.",
5861
+ description: "Wait for a file download to finish and return it as a real file on disk: {path,suggestedFilename,bytes,url,target}, written under the artifact dir's downloads/. ORDERING RULE, not optional: capture must be ARMED BEFORE the click that starts the download — call wait_for_download{arm:true} first (returns {armed:true,downloadPath,pending}), then click, then call again to collect the file. The download-behavior override is per-connection state Chrome REVERTS the moment the arming client disconnects, and an unarmed headless Chrome denies the download outright, so one triggered before arming is lost. SIDE EFFECT, browser-global: arming redirects EVERY download in this browser (all tabs, all origins) into the toolkit's downloads dir until this server exits — they no longer land in the user's normal Downloads folder. The arm lives on a connection this server holds open (an MCP-server capability): under the one-shot CLI the connection dies with the process and nothing is captured. Chrome-only (capability 'browser.downloads'): absent from tools/list under Firefox.",
5852
5862
  inputSchema: {
5853
5863
  type: "object",
5854
5864
  properties: {
5855
5865
  lease: {
5856
5866
  type: "string",
5857
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5867
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5858
5868
  },
5859
5869
  target: {
5860
5870
  type: "string",
5861
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'. A download is browser-scoped, so this names the tab for the lease check and for the echoed target, not which download is returned."
5871
+ description: "Names the tab for the lease check and the echoed target — a download is browser-scoped, so this does not pick which download is returned. Grammar: server instructions."
5862
5872
  },
5863
5873
  arm: {
5864
5874
  type: "boolean",
@@ -5866,7 +5876,7 @@ var MANIFEST = [
5866
5876
  },
5867
5877
  timeoutMs: {
5868
5878
  type: "number",
5869
- description: "How long to wait for a download to complete, in milliseconds. Default 30000. Ignored with arm:true. A download that already completed and was not yet collected is returned immediately, so this only bounds the wait for a download still in flight."
5879
+ description: "How long to wait for a download to complete, in milliseconds. Default 30000. Ignored with arm:true. A download that already completed and was not yet collected is returned immediately, so this only bounds the wait for one still in flight."
5870
5880
  }
5871
5881
  },
5872
5882
  required: [],
@@ -5875,17 +5885,17 @@ var MANIFEST = [
5875
5885
  },
5876
5886
  {
5877
5887
  name: "grant_permissions",
5878
- description: "Grant browser permissions for an origin up front, so a page asking for geolocation / notifications / clipboard gets an answer instead of showing a prompt no agent can click. 'permissions' takes CDP PermissionType values ('geolocation', 'notifications', 'clipboardReadWrite', 'camera', 'microphone', 'midi', ...); an unknown name is refused by Chrome with the bad value in the message. Returns {granted,origin,target}, or {reset:true,target} for a reset-only call. Grants are keyed by ORIGIN, not by tab: every tab on that origin is affected, including ones opened later. reset:true clears this server's previous grants first (or instead, when no permissions are given) — note CDP's reset is not origin-scoped, so it clears them for every origin at once. The grant lives on a connection this server process holds open and Chrome DISCARDS IT when that connection closes, so this is an MCP-server capability: under the one-shot CLI the grant dies with the process. Chrome-only (capability 'browser.permissions'): absent from tools/list under the Firefox backend, never present-and-throwing.",
5888
+ description: "Grant browser permissions for an origin up front, so a page asking for geolocation / notifications / clipboard gets an answer instead of a prompt no agent can click. 'permissions' takes CDP PermissionType values ('geolocation','notifications','clipboardReadWrite','camera',...); an unknown name is refused by Chrome, quoting the bad value. Returns {granted,origin,target}, or {reset:true,target} for a reset-only call. Grants are keyed by ORIGIN, not by tab: every tab on that origin is affected, including ones opened later. reset:true clears this server's previous grants first (or instead, when no permissions are given) — CDP's reset is not origin-scoped, so it clears them for every origin at once. The grant lives on a connection this server holds open and Chrome DISCARDS IT when that connection closes: an MCP-server capability, so under the one-shot CLI the grant dies with the process. Chrome-only (capability 'browser.permissions'): absent from tools/list under Firefox, never present-and-throwing.",
5879
5889
  inputSchema: {
5880
5890
  type: "object",
5881
5891
  properties: {
5882
5892
  lease: {
5883
5893
  type: "string",
5884
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5894
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5885
5895
  },
5886
5896
  target: {
5887
5897
  type: "string",
5888
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'. Used for the lease check and, when 'origin' is omitted, as the source of the origin to grant for."
5898
+ description: "Also the origin source when 'origin' is omitted. Grammar: server instructions."
5889
5899
  },
5890
5900
  permissions: {
5891
5901
  type: "array",
@@ -5909,29 +5919,29 @@ var MANIFEST = [
5909
5919
  },
5910
5920
  {
5911
5921
  name: "fill",
5912
- description: "Fill an element by focusing it, clearing existing content, then inserting 'value' via Input.insertText (atomic paste-like commit, not per-character keystrokes). Target with exactly one of 'uid' (a CDP backendDOMNodeId from take_snapshot) or 'selector' (a CSS selector).",
5922
+ description: "Fills an element by focusing it, clearing existing content, then inserting value via Input.insertText (atomic paste-like commit, not per-character keystrokes — won't fire per-keypress handlers). Give exactly one of uid or selector.",
5913
5923
  inputSchema: {
5914
5924
  type: "object",
5915
5925
  properties: {
5916
5926
  lease: {
5917
5927
  type: "string",
5918
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5928
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5919
5929
  },
5920
5930
  target: {
5921
5931
  type: "string",
5922
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5932
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5923
5933
  },
5924
5934
  uid: {
5925
5935
  type: "number",
5926
- description: "CDP backendDOMNodeId of the field to fill, obtained from take_snapshot. Provide exactly one of uid or selector."
5936
+ description: "CDP backendDOMNodeId of the field to fill, from take_snapshot. Exactly one of uid or selector."
5927
5937
  },
5928
5938
  selector: {
5929
5939
  type: "string",
5930
- description: "CSS selector for the field to fill (resolved via document.querySelector). Provide exactly one of uid or selector."
5940
+ description: "CSS selector for the field to fill (document.querySelector). Exactly one of uid or selector."
5931
5941
  },
5932
5942
  value: {
5933
5943
  type: "string",
5934
- description: "The text value to set; the field is cleared first so this overwrites existing content."
5944
+ description: "Text value to set; the field is cleared first, overwriting existing content."
5935
5945
  }
5936
5946
  },
5937
5947
  required: [
@@ -5942,17 +5952,17 @@ var MANIFEST = [
5942
5952
  },
5943
5953
  {
5944
5954
  name: "fill_form",
5945
- description: "Fill multiple form fields in one call; each field is focused, cleared, then set via Input.insertText. Each field in the non-empty 'fields' array takes exactly one of uid (a CDP backendDOMNodeId from take_snapshot) or selector, plus its string value.",
5955
+ description: "Fills multiple form fields in one call; each is focused, cleared, then set via Input.insertText (overwrites existing content, same as fill). Each entry in the non-empty 'fields' array takes exactly one of uid or selector plus a string value.",
5946
5956
  inputSchema: {
5947
5957
  type: "object",
5948
5958
  properties: {
5949
5959
  lease: {
5950
5960
  type: "string",
5951
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
5961
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5952
5962
  },
5953
5963
  target: {
5954
5964
  type: "string",
5955
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5965
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
5956
5966
  },
5957
5967
  fields: {
5958
5968
  type: "array",
@@ -5988,29 +5998,29 @@ var MANIFEST = [
5988
5998
  },
5989
5999
  {
5990
6000
  name: "type_text",
5991
- description: "Focus an element and append 'text' via Input.insertText without clearing first (closest to typing). Target with exactly one of 'uid' (a CDP backendDOMNodeId from take_snapshot) or 'selector' (a CSS selector).",
6001
+ description: "Focuses an element and appends text via Input.insertText without clearing first — closest primitive to typing, but still atomic (not per-character keystrokes). Give exactly one of uid or selector.",
5992
6002
  inputSchema: {
5993
6003
  type: "object",
5994
6004
  properties: {
5995
6005
  lease: {
5996
6006
  type: "string",
5997
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6007
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
5998
6008
  },
5999
6009
  target: {
6000
6010
  type: "string",
6001
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6011
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6002
6012
  },
6003
6013
  uid: {
6004
6014
  type: "number",
6005
- description: "CDP backendDOMNodeId of the element to type into, obtained from take_snapshot. Provide exactly one of uid or selector."
6015
+ description: "CDP backendDOMNodeId of the element to type into, from take_snapshot. Exactly one of uid or selector."
6006
6016
  },
6007
6017
  selector: {
6008
6018
  type: "string",
6009
- description: "CSS selector for the element to type into (resolved via document.querySelector). Provide exactly one of uid or selector."
6019
+ description: "CSS selector for the element to type into (document.querySelector). Exactly one of uid or selector."
6010
6020
  },
6011
6021
  text: {
6012
6022
  type: "string",
6013
- description: "The text to insert; appended to existing content rather than overwriting it."
6023
+ description: "Text to insert; appended to existing content rather than overwriting it."
6014
6024
  }
6015
6025
  },
6016
6026
  required: [
@@ -6021,25 +6031,25 @@ var MANIFEST = [
6021
6031
  },
6022
6032
  {
6023
6033
  name: "press_key",
6024
- description: "Dispatch a keyDown/keyUp pair for a single key with optional modifiers. 'key' is a named key (Enter, Tab, Escape, ArrowDown, Backspace, etc.) or a single printable character; the named-key table is curated (no F-keys/numpad/IME).",
6034
+ description: "Dispatches a keyDown/keyUp pair for one key with optional modifiers. 'key' is a named key (Enter, Tab, Escape, ArrowDown, Backspace, etc.) or a single printable character — the named-key table is curated, with no F-keys, numpad, or IME support.",
6025
6035
  inputSchema: {
6026
6036
  type: "object",
6027
6037
  properties: {
6028
6038
  lease: {
6029
6039
  type: "string",
6030
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6040
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6031
6041
  },
6032
6042
  target: {
6033
6043
  type: "string",
6034
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6044
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6035
6045
  },
6036
6046
  key: {
6037
6047
  type: "string",
6038
- description: "Key to press: a named key like 'Enter', 'Tab', 'Escape', 'ArrowDown', 'Backspace', or a single printable character. Required."
6048
+ description: "Key to press: a named key (Enter, Tab, Escape, ArrowDown, Backspace, ...) or a single printable character."
6039
6049
  },
6040
6050
  modifiers: {
6041
6051
  type: "array",
6042
- description: "Optional modifier names held during the press: 'Control'/'Ctrl', 'Shift', 'Alt', 'Meta'/'Cmd'.",
6052
+ description: "Modifier names held during the press: 'Control'/'Ctrl', 'Shift', 'Alt', 'Meta'/'Cmd'.",
6043
6053
  items: {
6044
6054
  type: "string"
6045
6055
  }
@@ -6053,32 +6063,32 @@ var MANIFEST = [
6053
6063
  },
6054
6064
  {
6055
6065
  name: "upload_file",
6056
- description: "Attach one or more files to an <input type=file> element via DOM.setFileInputFiles. Target the input with exactly one of 'uid' (a CDP backendDOMNodeId from take_snapshot) or 'selector'; 'files' is an absolute path or array of absolute paths.",
6066
+ description: "Attaches one or more files to an <input type=file> element via DOM.setFileInputFiles. Give exactly one of uid or selector for the input; 'files' is an array of absolute path(s) — wrap a single path in an array too.",
6057
6067
  inputSchema: {
6058
6068
  type: "object",
6059
6069
  properties: {
6060
6070
  lease: {
6061
6071
  type: "string",
6062
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6072
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6063
6073
  },
6064
6074
  target: {
6065
6075
  type: "string",
6066
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6076
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6067
6077
  },
6068
6078
  uid: {
6069
6079
  type: "number",
6070
- description: "CDP backendDOMNodeId of the file input element, obtained from take_snapshot. Provide exactly one of uid or selector."
6080
+ description: "CDP backendDOMNodeId of the file input element, from take_snapshot. Exactly one of uid or selector."
6071
6081
  },
6072
6082
  selector: {
6073
6083
  type: "string",
6074
- description: "CSS selector for the file input element (resolved via document.querySelector). Provide exactly one of uid or selector."
6084
+ description: "CSS selector for the file input element (document.querySelector). Exactly one of uid or selector."
6075
6085
  },
6076
6086
  files: {
6077
6087
  type: "array",
6078
6088
  items: {
6079
6089
  type: "string"
6080
6090
  },
6081
- description: 'Absolute path(s) to the file(s) to attach to the <input type=file>, as an array of absolute paths. A single path must be wrapped in an array (e.g. ["/tmp/a.pdf"]). Declared array-only because some model APIs (Google Gemini function calling) reject union-typed parameters.'
6091
+ description: 'Absolute path(s) to attach to the <input type=file>, as an array — wrap a single path too, e.g. ["/tmp/a.pdf"]. Array-only because some model APIs (Google Gemini function calling) reject union-typed parameters.'
6082
6092
  }
6083
6093
  },
6084
6094
  required: [
@@ -6089,17 +6099,17 @@ var MANIFEST = [
6089
6099
  },
6090
6100
  {
6091
6101
  name: "take_screenshot",
6092
- description: "Capture the viewport (default), the full scrollable page (fullPage), or a single element (uid or selector, exactly one and mutually exclusive) via raw CDP Page.captureScreenshot — as one image when it fits, or as vertical bands stitched losslessly into one PNG when it does not (see tile). Writes a PNG/JPEG under /tmp/cdp-toolkit (override with savePath; its directory is created for you) and returns {path,bytes,format,target,scale} plus width/height MEASURED from the encoded bytes, which are omitted rather than guessed when the bytes cannot be decoded; renderSize/renderRestored (+renderRestoreError) accompany a renderWidth/renderHeight capture, tiled/bands a banded one. Raw base64 is only included when returnBase64 is set, and is refused on a banded capture. quality applies to jpeg only. Every capture costs one Page.getLayoutMetrics round trip, because Chrome does not refuse an oversized capture politely: past 16384 device px per side Page.captureScreenshot can simply never answer, and captureBeyondViewport leaves the tab resized to the clip, so the size is checked before the capture rather than after.",
6102
+ description: "Captures the viewport (default), the full scrollable page (fullPage), or one element (uid or selector, mutually exclusive) via CDP Page.captureScreenshot. Past 16384 device px per side Chrome can hang rather than refuse, so size is checked first via Page.getLayoutMetrics; an oversized capture is auto-tiled into vertical bands stitched losslessly into one PNG (see tile) instead of wedging the tab. Writes PNG/JPEG to /tmp/cdp-toolkit (override savePath, dir auto-created); returns {path,bytes,format,target,scale} plus width/height measured from the encoded bytes (omitted if undecodable). renderSize/renderRestored(+renderRestoreError) accompany a renderWidth/renderHeight capture, tiled/bands a banded one. returnBase64 adds raw bytes to the result but is refused on a banded capture. quality applies to jpeg only.",
6093
6103
  inputSchema: {
6094
6104
  type: "object",
6095
6105
  properties: {
6096
6106
  lease: {
6097
6107
  type: "string",
6098
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6108
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6099
6109
  },
6100
6110
  target: {
6101
6111
  type: "string",
6102
- description: "Target page selector. undefined or 'active' -> first page target; '<32-hex targetId>' -> exact target by id; 'index:N' -> Nth page target (0-based); 'url:<substring>' -> first page whose url contains substring; 'title:<substring>' -> first page whose title contains substring; 'label:<name>' -> the page with exactly this label (origin ledger or live lease)."
6112
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6103
6113
  },
6104
6114
  format: {
6105
6115
  type: "string",
@@ -6107,35 +6117,35 @@ var MANIFEST = [
6107
6117
  "png",
6108
6118
  "jpeg"
6109
6119
  ],
6110
- description: "Image format. Defaults to png. quality applies only to jpeg."
6120
+ description: "Image format, default png. quality applies only to jpeg."
6111
6121
  },
6112
6122
  quality: {
6113
6123
  type: "number",
6114
- description: "JPEG quality 0-100 (default 80). Ignored for png."
6124
+ description: "JPEG quality 0-100, default 80; ignored for png."
6115
6125
  },
6116
6126
  fullPage: {
6117
6127
  type: "boolean",
6118
- description: "Capture the full scrollable content height computed from Page.getLayoutMetrics, not just the viewport."
6128
+ description: "Capture the full scrollable height (via Page.getLayoutMetrics), not just the viewport."
6119
6129
  },
6120
6130
  scale: {
6121
6131
  type: "number",
6122
- description: "Output-pixel multiplier for this one capture: greater than 0, at most 8, default 1. Output px = ceil(css px x scale x devicePixelRatio), so on a ratio-2 display scale 3 is 6x the CSS size; the page is never told anything changed (devicePixelRatio and innerWidth/innerHeight read identically before and after). Chrome only (capability screenshot.scale) — WebDriver BiDi's browsingContext.captureScreenshot has no scale parameter, so under Firefox call emulate with deviceScaleFactor first and capture at scale 1. A projection past Chrome's 16384 device px per side is refused before the browser is dialled, naming the largest scale that fits."
6132
+ description: "Output-pixel multiplier for this capture only (0<scale<=8, default 1): output px = ceil(css px x scale x devicePixelRatio); the page is never told anything changed. Chrome only (screenshot.scale) — Firefox BiDi has no scale param, use emulate{deviceScaleFactor} then scale:1. A projection over 16384 device px/side is refused up front, naming the largest scale that fits."
6123
6133
  },
6124
6134
  renderWidth: {
6125
6135
  type: "number",
6126
- description: "Emulate this viewport width, in CSS px (integer, 1-16384), for the length of ONE capture, then put the previous viewport back. REQUIRED together with renderHeight. Not the same knob as scale: scale re-renders the same layout at more pixels, this changes what the page BELIEVES, so media queries flip and the document re-lays-out — which is how you capture a responsive page at 1920x1080 from a tab that is not that size. Declared by BOTH backends (capability screenshot.renderSize). The restore can only put back an override this toolkit itself applied: one set by the DevTools UI, another client, or a pre-1.9.2 build is invisible and gets reset to the real device. The result reports renderSize and renderRestored; renderRestored:false means the tab is STILL emulated and you have to fix it with emulate."
6136
+ description: "Emulates this viewport width (CSS px, 1-16384) for one capture, then restores it. Required with renderHeight. Unlike scale, this changes what the page believes (media queries re-flow) — e.g. capture a responsive page at 1920x1080 from a differently-sized tab. Restore only works for an override this toolkit itself set; check result.renderRestored (false = still emulated, fix via emulate). Works on both backends (capability screenshot.renderSize)."
6127
6137
  },
6128
6138
  renderHeight: {
6129
6139
  type: "number",
6130
- description: "Emulate this viewport height, in CSS px (integer, 1-16384), for the length of one capture. Required together with renderWidth — a viewport is two numbers, and honouring one while inventing the other would render at a size you never named and never see in the result. Note a narrow renderWidth reflows most documents TALLER, which is the usual way a render-size capture trips the 16384-device-px encode limit."
6140
+ description: "Emulates this viewport height (CSS px, 1-16384) for one capture. Required with renderWidth — a viewport is two numbers. A narrow renderWidth often reflows pages TALLER, which is the usual way a render-size capture trips the 16384px encode limit."
6131
6141
  },
6132
6142
  tile: {
6133
6143
  type: "boolean",
6134
- description: "Whether this capture may be taken as vertical bands and stitched into one lossless PNG. Three-valued: omitted = AUTO (one shot when the projected output fits under Chrome's 16384 device px per side, bands when it does not — this is why a whole-page capture of a very long page now returns a file instead of hanging and wedging the tab); false = never band, refuse instead (the pre-1.9.2 behaviour); true = always band, even when one shot would have fitted. Only true costs a capability (screenshot.tile, Chrome only; Firefox was never driven for the band arithmetic). A banded capture is PNG-only and cannot also returnBase64, and only HEIGHT is tiled — a projected WIDTH past the limit is refused, not split, because bands are stitched by concatenating scanlines. Bands are a faithful crop of one document render: fixed and sticky elements do not repeat per band and seams are pixel-exact, but content the page only loads on real scroll (lazy images, virtualized lists) renders blank in bands the real viewport never reached, which is a property of the page and not of the stitch."
6144
+ description: "Whether to allow vertical-band stitching: omitted=AUTO (band only if a single shot wouldn't fit), false=never band (refuse instead), true=always band (Chrome-only capability). Banded output is PNG-only, can't returnBase64, and only bands on HEIGHT (oversized WIDTH is refused, not split). Bands are a faithful crop, but lazy/virtualized content the real viewport never scrolled to renders blank."
6135
6145
  },
6136
6146
  uid: {
6137
6147
  type: "number",
6138
- description: "Element to clip to: a CDP backendDOMNodeId obtained from take_snapshot. Mutually exclusive with selector."
6148
+ description: "Element to clip to: a backendDOMNodeId from take_snapshot. Mutually exclusive with selector."
6139
6149
  },
6140
6150
  selector: {
6141
6151
  type: "string",
@@ -6143,11 +6153,11 @@ var MANIFEST = [
6143
6153
  },
6144
6154
  savePath: {
6145
6155
  type: "string",
6146
- description: "Override the output file path. Default: /tmp/cdp-toolkit/screenshot-<id>-<stamp>.<ext>, where the stamp is the capture's START (a banded capture writes as it goes and has no finished image to name a file after). The destination directory is created from this path's own dirname, so a savePath outside /tmp/cdp-toolkit works."
6156
+ description: "Override the output path (default /tmp/cdp-toolkit/screenshot-<id>-<stamp>.<ext>, stamped at capture START). Directory is created from this path's dirname."
6147
6157
  },
6148
6158
  returnBase64: {
6149
6159
  type: "boolean",
6150
- description: "Also return the raw base64 image bytes in the result, in addition to writing the file. Refused on a banded capture (see tile): a stitched image runs to hundreds of megabytes and base64 inflates it by a third, into the caller's response — read the file at the returned path instead."
6160
+ description: "Also return raw base64 bytes in the result. Refused on a banded capture (see tile) — a stitched image can run to hundreds of MB; read the file at the returned path instead."
6151
6161
  }
6152
6162
  },
6153
6163
  required: [],
@@ -6156,41 +6166,41 @@ var MANIFEST = [
6156
6166
  },
6157
6167
  {
6158
6168
  name: "emulate",
6159
- description: "Apply any subset of Chrome emulation overrides in one call: device metrics (width/height together, plus deviceScaleFactor/mobile), userAgent, cpuThrottlingRate (>=1), emulated media type + mediaFeatures, and networkConditions. Pass clearOverrides:true to reset every override to the browser default (ignores all other fields); as of 1.9.2 that genuinely clears a device-metrics override set by a DIFFERENT process (Emulation.clearDeviceMetricsOverride is a no-op unless the clearing connection also set the override, so the driver re-asserts the tab's current size on its own connection first, then clears — and throws rather than answering cleared:true if it cannot verify that). Non-metrics overrides are session-scoped and reset when the per-call connection closes; device-metrics overrides persist on the target until cleared or the renderer navigates/reloads.",
6169
+ description: "Applies any subset of Chrome emulation overrides in one call: device metrics (width+height together, +deviceScaleFactor/mobile), userAgent, cpuThrottlingRate(>=1), media type+features, networkConditions. Device-metrics overrides PERSIST on the target until cleared or the renderer navigates/reloads; every other override is session-scoped and resets when this call's connection closes. clearOverrides:true resets everything (ignoring other fields) and, as of 1.9.2, genuinely clears a device-metrics override set by a DIFFERENT process — Chrome's clearDeviceMetricsOverride is normally a no-op unless the clearing connection also set the override, so the driver re-asserts the tab's current size first, then clears, throwing rather than reporting cleared:true if it can't verify.",
6160
6170
  inputSchema: {
6161
6171
  type: "object",
6162
6172
  properties: {
6163
6173
  lease: {
6164
6174
  type: "string",
6165
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6175
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6166
6176
  },
6167
6177
  target: {
6168
6178
  type: "string",
6169
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6179
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6170
6180
  },
6171
6181
  width: {
6172
6182
  type: "number",
6173
- description: "Device-metrics viewport width in CSS pixels. Must be supplied together with height."
6183
+ description: "Device-metrics viewport width in CSS px. Must be paired with height."
6174
6184
  },
6175
6185
  height: {
6176
6186
  type: "number",
6177
- description: "Device-metrics viewport height in CSS pixels. Must be supplied together with width."
6187
+ description: "Device-metrics viewport height in CSS px. Must be paired with width."
6178
6188
  },
6179
6189
  deviceScaleFactor: {
6180
6190
  type: "number",
6181
- description: "Device pixel ratio (DPR) for the device-metrics override; 0 uses the platform default."
6191
+ description: "Device pixel ratio for the device-metrics override; 0 = platform default."
6182
6192
  },
6183
6193
  mobile: {
6184
6194
  type: "boolean",
6185
- description: "Whether to emulate a mobile device (affects viewport meta handling, scrollbars, etc.) for the device-metrics override."
6195
+ description: "Emulate a mobile device (viewport meta, scrollbars, etc.) for the device-metrics override."
6186
6196
  },
6187
6197
  userAgent: {
6188
6198
  type: "string",
6189
- description: "User-Agent string to override via Emulation.setUserAgentOverride."
6199
+ description: "User-Agent string override (Emulation.setUserAgentOverride)."
6190
6200
  },
6191
6201
  cpuThrottlingRate: {
6192
6202
  type: "number",
6193
- description: "CPU throttling multiplier: 1 = no throttle, 2 = 2x slower, etc. Must be >= 1."
6203
+ description: "CPU throttling multiplier: 1=none, 2=2x slower, etc. Must be >=1."
6194
6204
  },
6195
6205
  media: {
6196
6206
  type: "string",
@@ -6198,7 +6208,7 @@ var MANIFEST = [
6198
6208
  },
6199
6209
  mediaFeatures: {
6200
6210
  type: "array",
6201
- description: "Emulated media features, e.g. [{ name: 'prefers-color-scheme', value: 'dark' }].",
6211
+ description: "Emulated media features, e.g. [{name:'prefers-color-scheme', value:'dark'}].",
6202
6212
  items: {
6203
6213
  type: "object",
6204
6214
  properties: {
@@ -6220,7 +6230,7 @@ var MANIFEST = [
6220
6230
  },
6221
6231
  networkConditions: {
6222
6232
  type: "object",
6223
- description: "Network condition overrides applied via Network.emulateNetworkConditions.",
6233
+ description: "Network overrides applied via Network.emulateNetworkConditions.",
6224
6234
  properties: {
6225
6235
  offline: {
6226
6236
  type: "boolean",
@@ -6247,7 +6257,7 @@ var MANIFEST = [
6247
6257
  },
6248
6258
  clearOverrides: {
6249
6259
  type: "boolean",
6250
- description: "Reset every override (device metrics, UA, CPU, media, network) to the browser default; ignores all other fields when true."
6260
+ description: "Reset every override (metrics, UA, CPU, media, network) to browser default; ignores all other fields when true."
6251
6261
  }
6252
6262
  },
6253
6263
  required: [],
@@ -6256,33 +6266,33 @@ var MANIFEST = [
6256
6266
  },
6257
6267
  {
6258
6268
  name: "resize_page",
6259
- description: "Set the page's device-metrics width/height (the narrow case of emulate), optionally with deviceScaleFactor and mobile, then verify by reading back window.innerWidth/innerHeight after the override is applied. Requires positive numeric width and height. The device-metrics override persists on the target until cleared or the renderer navigates/reloads.",
6269
+ description: "Sets the page's device-metrics width/height (the narrow case of emulate), optionally deviceScaleFactor/mobile, then verifies by reading back window.innerWidth/innerHeight after applying. Requires positive width and height. The override PERSISTS on the target until cleared (emulate{clearOverrides:true}) or the renderer navigates/reloads.",
6260
6270
  inputSchema: {
6261
6271
  type: "object",
6262
6272
  properties: {
6263
6273
  lease: {
6264
6274
  type: "string",
6265
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6275
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6266
6276
  },
6267
6277
  target: {
6268
6278
  type: "string",
6269
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6279
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6270
6280
  },
6271
6281
  width: {
6272
6282
  type: "number",
6273
- description: "Viewport width in CSS pixels (must be positive)."
6283
+ description: "Viewport width in CSS px (required, must be positive)."
6274
6284
  },
6275
6285
  height: {
6276
6286
  type: "number",
6277
- description: "Viewport height in CSS pixels (must be positive)."
6287
+ description: "Viewport height in CSS px (required, must be positive)."
6278
6288
  },
6279
6289
  deviceScaleFactor: {
6280
6290
  type: "number",
6281
- description: "Device pixel ratio (DPR); 0 uses the platform default."
6291
+ description: "Device pixel ratio; 0 = platform default."
6282
6292
  },
6283
6293
  mobile: {
6284
6294
  type: "boolean",
6285
- description: "Whether to emulate a mobile device for the device-metrics override."
6295
+ description: "Emulate a mobile device for the device-metrics override."
6286
6296
  }
6287
6297
  },
6288
6298
  required: [
@@ -6294,17 +6304,17 @@ var MANIFEST = [
6294
6304
  },
6295
6305
  {
6296
6306
  name: "handle_dialog",
6297
- description: "Wait for the next JavaScript dialog (alert/confirm/prompt/beforeunload) on a page and respond via Page.handleJavaScriptDialog, accepting or dismissing it. Default mode resolves with the first handled dialog or throws on timeout; set autoMs to handle every dialog opening during a fixed window and resolve with the list. The dialog must be triggered out-of-band (e.g. by clicking a button), since a blocking dialog freezes the renderer.",
6307
+ description: "Wait for the next JavaScript dialog (alert/confirm/prompt/beforeunload) on a page and respond via Page.handleJavaScriptDialog, accepting or dismissing it. Default mode resolves with the first handled dialog or throws on timeout; set autoMs to handle every dialog opening during a fixed window and resolve with the list. Trigger the dialog out-of-band (e.g. click a button) — a blocking dialog freezes the renderer, so it cannot be triggered inline.",
6298
6308
  inputSchema: {
6299
6309
  type: "object",
6300
6310
  properties: {
6301
6311
  lease: {
6302
6312
  type: "string",
6303
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6313
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6304
6314
  },
6305
6315
  target: {
6306
6316
  type: "string",
6307
- description: "Target page selector: 'active' (or omitted) for the first page-type target | 'index:N' (0-based) | 'url:<substring>' | 'title:<substring>' | 'label:<name>' (exact label match, origin ledger or live lease) | a 32-hex <targetId>."
6317
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6308
6318
  },
6309
6319
  accept: {
6310
6320
  type: "boolean",
@@ -6316,7 +6326,7 @@ var MANIFEST = [
6316
6326
  },
6317
6327
  timeoutMs: {
6318
6328
  type: "number",
6319
- description: "How long to wait for the next dialog in milliseconds (default 15000)."
6329
+ description: "How long to wait for the next dialog, in milliseconds (default 15000)."
6320
6330
  },
6321
6331
  autoMs: {
6322
6332
  type: "number",
@@ -6331,29 +6341,29 @@ var MANIFEST = [
6331
6341
  },
6332
6342
  {
6333
6343
  name: "list_console_messages",
6334
- description: "Read console output (logs, warnings, exceptions) captured for the target page. By default reads the target's existing shared buffer and returns parsed console entries (empty if no capture has run); with reload:true it reloads the page and records a fresh capture window (both console+network) so a network reload never wipes console history. SERVICE WORKERS (Chrome only, Capability 'worker.targets'): pass target 'worker:<extension-id>' to watch an MV3 extension's BACKGROUND SERVICE WORKER. The outbound request that worker makes to your backend is recorded from CDP's Network domain on the worker's own session, which observes what the code ACTUALLY SENT and so needs neither of the two tricks a caller reaches for first. Both of those fail for ordinary JavaScript reasons rather than service-worker ones: a value bound at module top level is not visible from evaluate_script's global scope (plain lexical scoping, exactly the same in a page), and assigning self.fetch cannot rebind a fetch reference the module already captured. A worker's console.log arrives as Runtime.consoleAPICalled on its own session. A worker capture has no reload to drive it: reload:true means 'listen for durationMs while the worker runs', so trigger the worker's work (another tool call, an extension message, an alarm) during the window; a worker woken by the call has already run its top-level code by the time the recorder attaches, so a request it makes at startup can be missed. SIDE EFFECT, deliberate and documented: the recording holds a CDP session on the worker, and that KEEPS THE WORKER ALIVE for as long as the capture runs — an MV3 worker is otherwise idle-evicted within seconds — and Chrome resumes evicting it once the capture stops.",
6344
+ description: "Read console output (logs, warnings, exceptions) captured for the target. Reads the existing shared buffer by default (empty if no capture ran); reload:true reloads and records a fresh capture (console+network together, so a network reload never wipes console history). WORKER capture (Chrome, Capability 'worker.targets'): a worker: target records an MV3 background service worker -- its console.log arrives as Runtime.consoleAPICalled, its outbound requests from CDP's Network domain on the worker's own session -- so the two tricks callers try first fail for plain JS reasons, not worker ones: a value bound at module top level is not visible from evaluate_script's global scope, and assigning self.fetch cannot rebind a fetch the module already captured. A worker has no reload: reload:true means 'listen for durationMs while it runs', so trigger its work (tool call, message, alarm) during the window; a worker woken by the call already ran its top-level code, so a startup message can be missed. SIDE EFFECT: the CDP session KEEPS THE WORKER ALIVE for the capture (an idle MV3 worker is evicted in seconds); Chrome resumes evicting after.",
6335
6345
  inputSchema: {
6336
6346
  type: "object",
6337
6347
  properties: {
6338
6348
  lease: {
6339
6349
  type: "string",
6340
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6350
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6341
6351
  },
6342
6352
  target: {
6343
6353
  type: "string",
6344
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>' | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js). The worker: arm is CHROME-ONLY (Capability 'worker.targets'), is refused on Firefox, and a bare worker target id works too."
6354
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
6345
6355
  },
6346
6356
  reload: {
6347
6357
  type: "boolean",
6348
- description: "Record fresh by reloading the page and capturing for a window. Default false (read the existing buffer). With a worker: target there is nothing to reload: the window records for durationMs while the worker runs, and the session it holds keeps that worker from being idle-evicted until the capture stops."
6358
+ description: "Record fresh by reloading the page and capturing a window. Default false (read existing buffer). With a worker: target nothing reloads -- records for durationMs while the worker runs and keeps it alive until the capture stops."
6349
6359
  },
6350
6360
  durationMs: {
6351
6361
  type: "number",
6352
- description: "Capture window for reload mode, in milliseconds. Default 2500."
6362
+ description: "Capture window for reload mode, in ms. Default 2500."
6353
6363
  },
6354
6364
  wake: {
6355
6365
  type: "boolean",
6356
- description: "Worker targets only, and only when a capture is being started (reload:true). Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so the capture asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything. Rejected rather than ignored on a page target, on a bare target id (Chrome re-mints a worker's target id when it restarts, so only the url-substring form can name a worker that is not running), and on a read-only call (the buffer is keyed by target id, so a woken worker's buffer would be empty)."
6366
+ description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
6357
6367
  }
6358
6368
  },
6359
6369
  required: [],
@@ -6362,17 +6372,17 @@ var MANIFEST = [
6362
6372
  },
6363
6373
  {
6364
6374
  name: "get_console_message",
6365
- description: "Return a single console entry by zero-based index from the target's existing console buffer. Throws if the index is out of range; run list_console_messages (optionally with reload:true) first to populate the buffer.",
6375
+ description: "Return one console entry by zero-based index from the target's existing console buffer. Throws if the index is out of range; run list_console_messages (optionally reload:true) first to populate the buffer.",
6366
6376
  inputSchema: {
6367
6377
  type: "object",
6368
6378
  properties: {
6369
6379
  lease: {
6370
6380
  type: "string",
6371
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6381
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6372
6382
  },
6373
6383
  target: {
6374
6384
  type: "string",
6375
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
6385
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6376
6386
  },
6377
6387
  index: {
6378
6388
  type: "number",
@@ -6385,33 +6395,33 @@ var MANIFEST = [
6385
6395
  },
6386
6396
  {
6387
6397
  name: "list_network_requests",
6388
- description: "Return correlated network request rows (one per requestId, with status/headers/state) for the target page. By default reads the target's existing buffer; with reload:true it reloads and records a fresh both-domains capture window. Use filterUrl to keep only requests whose URL contains a substring. SERVICE WORKERS (Chrome only, Capability 'worker.targets'): pass target 'worker:<extension-id>' to watch an MV3 extension's BACKGROUND SERVICE WORKER. The outbound request that worker makes to your backend is recorded from CDP's Network domain on the worker's own session, which observes what the code ACTUALLY SENT and so needs neither of the two tricks a caller reaches for first. Both of those fail for ordinary JavaScript reasons rather than service-worker ones: a value bound at module top level is not visible from evaluate_script's global scope (plain lexical scoping, exactly the same in a page), and assigning self.fetch cannot rebind a fetch reference the module already captured. A worker capture has no reload to drive it: reload:true means 'listen for durationMs while the worker runs', so trigger the worker's work (another tool call, an extension message, an alarm) during the window; a worker woken by the call has already run its top-level code by the time the recorder attaches, so a request it makes at startup can be missed. SIDE EFFECT, deliberate and documented: the recording holds a CDP session on the worker, and that KEEPS THE WORKER ALIVE for as long as the capture runs — an MV3 worker is otherwise idle-evicted within seconds — and Chrome resumes evicting it once the capture stops.",
6398
+ description: "Return correlated network rows (one per requestId; status/headers/state) for the target. Reads the existing buffer by default; reload:true records a fresh both-domains capture window. filterUrl keeps only URLs containing a substring. WORKER capture (Chrome, Capability 'worker.targets'): a worker: target records an MV3 background service worker's outbound requests from CDP's Network domain on the worker's own session -- what the code ACTUALLY SENT -- so the two tricks callers try first fail for plain JS reasons, not worker ones: a value bound at module top level is not visible from evaluate_script's global scope, and assigning self.fetch cannot rebind a fetch the module already captured. A worker has no reload: reload:true means 'listen for durationMs while it runs', so trigger its work (tool call, message, alarm) during the window; a worker woken by the call already ran its top-level code, so a startup request can be missed. SIDE EFFECT: the CDP session KEEPS THE WORKER ALIVE for the capture (an idle MV3 worker is evicted in seconds); Chrome resumes evicting after.",
6389
6399
  inputSchema: {
6390
6400
  type: "object",
6391
6401
  properties: {
6392
6402
  lease: {
6393
6403
  type: "string",
6394
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6404
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6395
6405
  },
6396
6406
  target: {
6397
6407
  type: "string",
6398
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>' | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js). The worker: arm is CHROME-ONLY (Capability 'worker.targets'), is refused on Firefox, and a bare worker target id works too."
6408
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
6399
6409
  },
6400
6410
  reload: {
6401
6411
  type: "boolean",
6402
- description: "Record fresh by reloading the page and capturing for a window. Default false (read the existing buffer). With a worker: target there is nothing to reload: the window records for durationMs while the worker runs, and the session it holds keeps that worker from being idle-evicted until the capture stops."
6412
+ description: "Record fresh by reloading the page and capturing a window. Default false (read existing buffer). With a worker: target nothing reloads -- records for durationMs while the worker runs and keeps it alive until the capture stops."
6403
6413
  },
6404
6414
  durationMs: {
6405
6415
  type: "number",
6406
- description: "Capture window for reload mode, in milliseconds. Default 2500."
6416
+ description: "Capture window for reload mode, in ms. Default 2500."
6407
6417
  },
6408
6418
  filterUrl: {
6409
6419
  type: "string",
6410
- description: "Only return requests whose URL contains this substring."
6420
+ description: "Return only requests whose URL contains this substring."
6411
6421
  },
6412
6422
  wake: {
6413
6423
  type: "boolean",
6414
- description: "Worker targets only, and only when a capture is being started (reload:true). Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so the capture asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything. Rejected rather than ignored on a page target, on a bare target id (Chrome re-mints a worker's target id when it restarts, so only the url-substring form can name a worker that is not running), and on a read-only call (the buffer is keyed by target id, so a woken worker's buffer would be empty)."
6424
+ description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
6415
6425
  }
6416
6426
  },
6417
6427
  required: [],
@@ -6420,21 +6430,21 @@ var MANIFEST = [
6420
6430
  },
6421
6431
  {
6422
6432
  name: "get_network_request",
6423
- description: "Return one network request (matched by exact requestId, else by url substring) including status/headers. Requires at least one of requestId or url (throws otherwise). With includeBody:true the body fetch drives a fresh capture (a reload for a page, a listen window for a worker) and is matched by url ONLY (a fresh capture re-mints requestIds, so a carried-over requestId cannot fetch a body, so it returns metadata plus bodyUnavailableReason). SERVICE WORKERS (Chrome only, Capability 'worker.targets'): pass target 'worker:<extension-id>' to watch an MV3 extension's BACKGROUND SERVICE WORKER. The outbound request that worker makes to your backend is recorded from CDP's Network domain on the worker's own session, which observes what the code ACTUALLY SENT and so needs neither of the two tricks a caller reaches for first. Both of those fail for ordinary JavaScript reasons rather than service-worker ones: a value bound at module top level is not visible from evaluate_script's global scope (plain lexical scoping, exactly the same in a page), and assigning self.fetch cannot rebind a fetch reference the module already captured. A worker capture has no reload to drive it: reload:true means 'listen for durationMs while the worker runs', so trigger the worker's work (another tool call, an extension message, an alarm) during the window; a worker woken by the call has already run its top-level code by the time the recorder attaches, so a request it makes at startup can be missed. SIDE EFFECT, deliberate and documented: the recording holds a CDP session on the worker, and that KEEPS THE WORKER ALIVE for as long as the capture runs — an MV3 worker is otherwise idle-evicted within seconds — and Chrome resumes evicting it once the capture stops.",
6433
+ description: "Return one network request (matched by exact requestId, else url substring) with status/headers. Requires requestId or url (throws otherwise). includeBody:true drives a fresh capture (page reload, or worker listen window) matched by url ONLY -- a fresh capture re-mints requestIds, so a carried-over requestId returns metadata plus bodyUnavailableReason. WORKER capture (Chrome, Capability 'worker.targets'): a worker: target records an MV3 background service worker's outbound requests from CDP's Network domain on the worker's own session -- what the code ACTUALLY SENT -- so the two tricks callers try first fail for plain JS reasons, not worker ones: a value bound at module top level is not visible from evaluate_script's global scope, and assigning self.fetch cannot rebind a fetch the module already captured. A worker has no reload: reload means 'listen for durationMs while it runs', so trigger its work (tool call, message, alarm) during the window; a worker woken by the call already ran its top-level code, so a startup request can be missed. SIDE EFFECT: the CDP session KEEPS THE WORKER ALIVE for the capture (an idle MV3 worker is evicted in seconds); Chrome resumes evicting after.",
6424
6434
  inputSchema: {
6425
6435
  type: "object",
6426
6436
  properties: {
6427
6437
  lease: {
6428
6438
  type: "string",
6429
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6439
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6430
6440
  },
6431
6441
  target: {
6432
6442
  type: "string",
6433
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>' | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js). The worker: arm is CHROME-ONLY (Capability 'worker.targets'), is refused on Firefox, and a bare worker target id works too."
6443
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', '<targetId>'; plus 'worker:<substring>' to reach a service/shared worker (e.g. an MV3 extension background worker). The worker: arm is CHROME-ONLY (Capability 'worker.targets'). Grammar: see server instructions."
6434
6444
  },
6435
6445
  requestId: {
6436
6446
  type: "string",
6437
- description: "Match by exact requestId (metadata only, cannot fetch a body, since reload re-mints requestIds)."
6447
+ description: "Match by exact requestId (metadata only; cannot fetch a body, since a reload re-mints requestIds)."
6438
6448
  },
6439
6449
  url: {
6440
6450
  type: "string",
@@ -6442,15 +6452,15 @@ var MANIFEST = [
6442
6452
  },
6443
6453
  includeBody: {
6444
6454
  type: "boolean",
6445
- description: "Also fetch the response body; drives a fresh reload capture and must be used with the `url` selector."
6455
+ description: "Also fetch the response body; drives a fresh capture and requires the url selector."
6446
6456
  },
6447
6457
  durationMs: {
6448
6458
  type: "number",
6449
- description: "Capture window for the reload-driven body fetch, in milliseconds. Default 2500."
6459
+ description: "Capture window for the body-fetch reload, in ms. Default 2500."
6450
6460
  },
6451
6461
  wake: {
6452
6462
  type: "boolean",
6453
- description: "Worker targets only, and only when a capture is being started (includeBody together with url). Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so the capture asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything. Rejected rather than ignored on a page target, on a bare target id (Chrome re-mints a worker's target id when it restarts, so only the url-substring form can name a worker that is not running), and on a read-only call (the buffer is keyed by target id, so a woken worker's buffer would be empty)."
6463
+ description: "Worker targets only, when starting a capture. Default true: an idle-evicted MV3 service worker exists in no target listing, so it is started first (ServiceWorker.startWorker) and re-confirmed; false fails fast. Rejected on a page target, a bare target id, or a read-only call. See server instructions."
6454
6464
  }
6455
6465
  },
6456
6466
  required: [],
@@ -6459,24 +6469,24 @@ var MANIFEST = [
6459
6469
  },
6460
6470
  {
6461
6471
  name: "performance_start_trace",
6462
- description: "Start a performance trace on the target page over raw CDP (Tracing.start) and park the recording connection in-process. Must be paired with performance_stop_trace WITHIN THE SAME PROCESS; for a robust cross-call trace use performance_trace instead. Throws if a trace is already in progress for the target.",
6472
+ description: "Starts a CDP trace recording on the target and holds the recording connection in-process; you MUST call performance_stop_trace from the SAME PROCESS to end it, or use performance_trace instead for a robust cross-call trace. Throws if a trace is already in progress for this target.",
6463
6473
  inputSchema: {
6464
6474
  type: "object",
6465
6475
  properties: {
6466
6476
  lease: {
6467
6477
  type: "string",
6468
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6478
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6469
6479
  },
6470
6480
  target: {
6471
6481
  type: "string",
6472
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6482
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6473
6483
  },
6474
6484
  categories: {
6475
6485
  type: "array",
6476
6486
  items: {
6477
6487
  type: "string"
6478
6488
  },
6479
- description: "Trace categories to include. Defaults to the timeline + user-timing + loading + disabled-by-default timeline tracks that carry LCP/LayoutShift/RunTask."
6489
+ description: "Trace categories to record. Defaults to timeline + user-timing + loading + disabled-by-default timeline tracks carrying LCP/LayoutShift/RunTask."
6480
6490
  }
6481
6491
  },
6482
6492
  required: [],
@@ -6485,17 +6495,17 @@ var MANIFEST = [
6485
6495
  },
6486
6496
  {
6487
6497
  name: "performance_stop_trace",
6488
- description: "Stop the in-process trace started by performance_start_trace, drain buffered Tracing.dataCollected events, write the trace JSON under /tmp/cdp-toolkit, and return {path,bytes,events,metrics}. Throws if no live trace exists in this process (e.g. start ran in a different process); use performance_trace instead. The 'target' arg is accepted only for API symmetry; at most one trace is ever live per process.",
6498
+ description: "Stops the in-process trace started by performance_start_trace and writes it as JSON under /tmp/cdp-toolkit, returning {path,bytes,events,metrics}. Throws if no live trace exists in THIS process (e.g. start ran in a different process) — use performance_trace instead. The target arg is accepted only for API symmetry; at most one trace is ever live per process.",
6489
6499
  inputSchema: {
6490
6500
  type: "object",
6491
6501
  properties: {
6492
6502
  lease: {
6493
6503
  type: "string",
6494
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6504
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6495
6505
  },
6496
6506
  target: {
6497
6507
  type: "string",
6498
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6508
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6499
6509
  }
6500
6510
  },
6501
6511
  required: [],
@@ -6504,17 +6514,17 @@ var MANIFEST = [
6504
6514
  },
6505
6515
  {
6506
6516
  name: "performance_analyze_insight",
6507
- description: "CDP-native approximation of the DevTools insight analyzer: read a trace JSON file (bare array or {traceEvents:[...]}) at the given tracePath and return headline metrics (FCP/LCP/CLS/TBT, long tasks, layout shifts). Requires an explicit tracePath returned by performance_trace/performance_stop_trace; there is no implicit 'latest trace'. Numbers approximate DevTools (no main-thread attribution or frame-scoped LCP).",
6517
+ description: "Reads a trace JSON file (from performance_trace/performance_stop_trace) at tracePath and returns headline metrics: FCP/LCP/CLS/TBT, long tasks, layout shifts. Requires an EXPLICIT tracePath — there is no implicit 'latest trace'. Numbers approximate DevTools's insight analyzer (no main-thread attribution or frame-scoped LCP).",
6508
6518
  inputSchema: {
6509
6519
  type: "object",
6510
6520
  properties: {
6511
6521
  lease: {
6512
6522
  type: "string",
6513
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6523
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6514
6524
  },
6515
6525
  tracePath: {
6516
6526
  type: "string",
6517
- description: "Filesystem path to a trace JSON file written by performance_trace or performance_stop_trace (bare array OR {traceEvents:[...]}). Required."
6527
+ description: "Path to a trace JSON file (bare array or {traceEvents:[...]}) written by performance_trace or performance_stop_trace. Required."
6518
6528
  }
6519
6529
  },
6520
6530
  required: [],
@@ -6523,25 +6533,25 @@ var MANIFEST = [
6523
6533
  },
6524
6534
  {
6525
6535
  name: "performance_trace",
6526
- description: "PRIMARY one-shot trace: start tracing, optionally reload or navigate the page, wait durationMs (default 3000), end the trace, write the trace JSON under /tmp/cdp-toolkit, and return {path,bytes,events,metrics,target}. Holds one connection open for the whole window, so it is immune to the cross-process limitation of start/stop, and it is the recommended entry point.",
6536
+ description: "RECOMMENDED entry point: one-shot trace that starts tracing, optionally reloads or navigates the page, waits durationMs (default 3000ms), stops, and writes JSON under /tmp/cdp-toolkit — returns {path,bytes,events,metrics,target}. Holds one connection open for the whole window, so unlike start/stop it is NOT limited to a single process.",
6527
6537
  inputSchema: {
6528
6538
  type: "object",
6529
6539
  properties: {
6530
6540
  lease: {
6531
6541
  type: "string",
6532
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6542
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6533
6543
  },
6534
6544
  target: {
6535
6545
  type: "string",
6536
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6546
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6537
6547
  },
6538
6548
  durationMs: {
6539
6549
  type: "number",
6540
- description: "How long to record after the optional reload/navigate, in milliseconds. Default 3000."
6550
+ description: "How long to record after the optional reload/navigate, in ms. Default 3000."
6541
6551
  },
6542
6552
  reload: {
6543
6553
  type: "boolean",
6544
- description: "Reload the page after starting the trace to capture full navigation timing."
6554
+ description: "Reload the page after starting the trace, to capture full navigation timing."
6545
6555
  },
6546
6556
  navigateTo: {
6547
6557
  type: "string",
@@ -6552,7 +6562,7 @@ var MANIFEST = [
6552
6562
  items: {
6553
6563
  type: "string"
6554
6564
  },
6555
- description: "Trace categories to include. Defaults to the timeline + user-timing + loading + disabled-by-default timeline tracks that carry LCP/LayoutShift/RunTask."
6565
+ description: "Trace categories to record. Defaults to timeline + user-timing + loading + disabled-by-default timeline tracks carrying LCP/LayoutShift/RunTask."
6556
6566
  }
6557
6567
  },
6558
6568
  required: [],
@@ -6561,17 +6571,17 @@ var MANIFEST = [
6561
6571
  },
6562
6572
  {
6563
6573
  name: "start_screen_recording",
6564
- description: "Start recording the target tab to video over raw CDP (Page.startScreencast), holding one persistent connection open and spooling every Page.screencastFrame to disk. Chrome emits a frame ON REPAINT, not on a clock, so the stream is variable-rate: each frame is timestamped into a ledger and stop_screen_recording turns those timestamps into per-frame display durations, which is what makes a still page hold one long frame instead of the video racing or freezing. Every frame is acked immediately (an unacked frame stalls the stream). ffmpeg is probed here, not at stop, so a missing encoder fails before a recording is captured and thrown away. Must be paired with stop_screen_recording WITHIN THE SAME PROCESS (the frames are events on this connection and cannot be re-attached from another process, so the pair works under the MCP server, not the one-process-per-call CLI). Throws if a recording is already in progress for the target; recordings on DIFFERENT targets run concurrently. Chrome only: WebDriver BiDi has no screencast primitive, so both tools are absent under --browser firefox.",
6574
+ description: "Starts recording the target tab to video via CDP Page.startScreencast, holding one persistent connection and spooling every frame to disk — MUST be paired with stop_screen_recording WITHIN THE SAME PROCESS (frames are connection-scoped and cannot be reattached from another process, so this only works under the MCP server, not the one-process-per-call CLI). Chrome only: WebDriver BiDi has no screencast primitive, so both tools are absent under --browser firefox. Frames arrive ON REPAINT not on a clock (variable rate); each is timestamped into a ledger that stop_screen_recording turns into per-frame display durations, so a still page holds one long frame instead of racing. ffmpeg is probed here (not at stop) so a missing encoder fails before frames are captured and thrown away. Throws if the target already has a recording in progress; different targets record concurrently.",
6565
6575
  inputSchema: {
6566
6576
  type: "object",
6567
6577
  properties: {
6568
6578
  lease: {
6569
6579
  type: "string",
6570
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6580
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6571
6581
  },
6572
6582
  target: {
6573
6583
  type: "string",
6574
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6584
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6575
6585
  },
6576
6586
  format: {
6577
6587
  type: "string",
@@ -6579,27 +6589,27 @@ var MANIFEST = [
6579
6589
  "jpeg",
6580
6590
  "png"
6581
6591
  ],
6582
- description: "Frame image format. Defaults to jpeg (far smaller per repaint); png is lossless. quality applies to jpeg only."
6592
+ description: "Frame format, default jpeg (much smaller per repaint); png is lossless. quality applies to jpeg only."
6583
6593
  },
6584
6594
  quality: {
6585
6595
  type: "number",
6586
- description: "JPEG frame quality 0-100. Ignored for png. Omitted by default, which leaves Chrome's own default in place."
6596
+ description: "JPEG frame quality 0-100. Ignored for png; omitted leaves Chrome's own default."
6587
6597
  },
6588
6598
  maxWidth: {
6589
6599
  type: "number",
6590
- description: "Cap the streamed frame width in pixels. Also the way to pin frame size if the viewport might change mid-recording."
6600
+ description: "Caps the streamed frame width in px; also pins frame size if the viewport may change mid-recording."
6591
6601
  },
6592
6602
  maxHeight: {
6593
6603
  type: "number",
6594
- description: "Cap the streamed frame height in pixels."
6604
+ description: "Caps the streamed frame height in px."
6595
6605
  },
6596
6606
  everyNthFrame: {
6597
6607
  type: "number",
6598
- description: "Capture only every Nth repaint (1 = every frame). Reduces spool size on a busy page; per-frame durations keep playback at wallclock speed either way."
6608
+ description: "Capture only every Nth repaint (1=every frame), to shrink spool size on a busy page; per-frame durations keep playback at wallclock speed either way."
6599
6609
  },
6600
6610
  bringToFront: {
6601
6611
  type: "boolean",
6602
- description: "Activate the tab (Page.bringToFront) before recording. Default false. A fully backgrounded or occluded tab may never repaint, and a recording of one captures 0 frames."
6612
+ description: "Activate the tab (Page.bringToFront) before recording, default false. A backgrounded/occluded tab may never repaint, yielding a 0-frame recording."
6603
6613
  }
6604
6614
  },
6605
6615
  required: [],
@@ -6608,21 +6618,21 @@ var MANIFEST = [
6608
6618
  },
6609
6619
  {
6610
6620
  name: "stop_screen_recording",
6611
- description: "Stop the recording started by start_screen_recording, assemble the spooled frames into an MP4 with ffmpeg using PER-FRAME durations from the ledger (frame N is held until frame N+1 painted; the last frame is held until this call), and return {path,bytes,durationMs,frameCount,encodedFrames,codec,encoder,width,height,droppedFrames,target}. Encoder ladder, probed once at start: hevc_videotoolbox then h264_videotoolbox then libx265 then libx264, with -tag:v hvc1 on HEVC so QuickTime plays it, +faststart, yuv420p, and even dimensions forced. With one recording in flight 'target' may be omitted; with several it is required, because guessing which to stop can lose another agent's recording. Throws if nothing is recording in this process, and throws rather than writing a silent empty video when 0 frames arrived. On an ffmpeg failure the spooled frames are KEPT and the spool path plus the exact command are named in the error. Encoded video tops out at 25 fps: ffmpeg's concat demuxer represents image timestamps on a 1/25s grid, so frames captured less than 40ms apart are coalesced deliberately (frameCount reports what was captured, encodedFrames what reached the video) instead of being dropped silently by ffmpeg. durationMs is the sum of the encoded durations; the file itself runs one 40ms step longer, because the concat demuxer needs the final frame repeated for its hold to count.",
6621
+ description: "Stops the recording started by start_screen_recording, assembling spooled frames into an MP4 via ffmpeg with PER-FRAME durations (frame N held until N+1 painted; last frame held until this call) — capped at 25fps by ffmpeg's concat demuxer, so frames under 40ms apart are deliberately coalesced (frameCount=captured, encodedFrames=what reached the video, never silently dropped). Returns {path,bytes,durationMs,frameCount,encodedFrames,codec,encoder,width,height,droppedFrames,target}; durationMs sums encoded durations, the file runs one 40ms step longer (final frame repeated). Encoder ladder (probed at start): hevc_videotoolbox>h264_videotoolbox>libx265>libx264 (+hvc1 tag, faststart, yuv420p, even dims). target: omit with one recording in flight, required with several (avoids guessing which to stop). Throws if nothing is recording in this process, or if 0 frames arrived (no silent empty video). On ffmpeg failure the spooled frames are KEPT — spool path + exact command are in the error.",
6612
6622
  inputSchema: {
6613
6623
  type: "object",
6614
6624
  properties: {
6615
6625
  lease: {
6616
6626
  type: "string",
6617
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6627
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6618
6628
  },
6619
6629
  target: {
6620
6630
  type: "string",
6621
- description: "Which recording to stop: 'active' (default, valid only when exactly one recording is in flight) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6631
+ description: "Which recording to stop — 'active' valid only when exactly one recording is in flight. Grammar: server instructions."
6622
6632
  },
6623
6633
  savePath: {
6624
6634
  type: "string",
6625
- description: "Override the output file path. Default: /tmp/cdp-toolkit/screen-recording-<targetIdShort>-<stamp>.mp4."
6635
+ description: "Override the output path (default /tmp/cdp-toolkit/screen-recording-<targetIdShort>-<stamp>.mp4)."
6626
6636
  }
6627
6637
  },
6628
6638
  required: [],
@@ -6631,21 +6641,21 @@ var MANIFEST = [
6631
6641
  },
6632
6642
  {
6633
6643
  name: "take_heapsnapshot",
6634
- description: "Capture a V8 heap snapshot of the selected page target over raw CDP (HeapProfiler.takeHeapSnapshot, accumulating addHeapSnapshotChunk events) and write it as a .heapsnapshot JSON file loadable by the DevTools Memory panel; returns {path,bytes,chunks,target} only and does not parse/summarize the snapshot. Writes under /tmp/cdp-toolkit (CDP_ARTIFACT_DIR) unless savePath is given.",
6644
+ description: "Captures a V8 heap snapshot of the target page over CDP and writes it as a .heapsnapshot JSON file (loadable in the DevTools Memory panel) under /tmp/cdp-toolkit unless savePath is given. Returns {path,bytes,chunks,target} only — does NOT parse or summarize the snapshot.",
6635
6645
  inputSchema: {
6636
6646
  type: "object",
6637
6647
  properties: {
6638
6648
  lease: {
6639
6649
  type: "string",
6640
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6650
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6641
6651
  },
6642
6652
  target: {
6643
6653
  type: "string",
6644
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
6654
+ description: "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions."
6645
6655
  },
6646
6656
  savePath: {
6647
6657
  type: "string",
6648
- description: "Override the output path. An absolute path (starting with /) is used as-is; a relative path is resolved under the artifact dir (/tmp/cdp-toolkit). Defaults to an auto-named take_heapsnapshot-<targetId>-<timestamp>.heapsnapshot file."
6658
+ description: "Override the output path. Absolute paths (starting with /) are used as-is; relative paths resolve under the artifact dir (/tmp/cdp-toolkit). Defaults to an auto-named take_heapsnapshot-<targetId>-<timestamp>.heapsnapshot file."
6649
6659
  }
6650
6660
  },
6651
6661
  required: [],
@@ -6654,24 +6664,24 @@ var MANIFEST = [
6654
6664
  },
6655
6665
  {
6656
6666
  name: "lighthouse_audit",
6657
- description: "Run a Lighthouse audit against a URL by shelling out to `npx --yes lighthouse` (the toolkit's sole non-CDP tool); Lighthouse attaches to the already-running Chrome on the remote-debugging port and audits its own about:blank tab rather than any live user tab. Writes a JSON report under /tmp/cdp-toolkit and returns {path, bytes} plus per-category scores (0..1 or null).",
6667
+ description: "Runs a Lighthouse audit against a URL by shelling out to `npx --yes lighthouse` (the toolkit's only non-CDP tool) — it attaches to the already-running Chrome and audits its OWN about:blank tab, NOT any live user tab you've navigated. Writes a JSON report under /tmp/cdp-toolkit; returns {path,bytes} plus per-category scores (0..1 or null).",
6658
6668
  inputSchema: {
6659
6669
  type: "object",
6660
6670
  properties: {
6661
6671
  lease: {
6662
6672
  type: "string",
6663
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6673
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6664
6674
  },
6665
6675
  url: {
6666
6676
  type: "string",
6667
- description: "The URL to audit. Required, and never points at a user tab implicitly."
6677
+ description: "The URL to audit. Required — never points at a user tab implicitly."
6668
6678
  },
6669
6679
  categories: {
6670
6680
  type: "array",
6671
6681
  items: {
6672
6682
  type: "string"
6673
6683
  },
6674
- description: 'Lighthouse categories to run (passed as --only-categories). Defaults to the full set, e.g. ["performance"] or ["performance","accessibility","seo"].'
6684
+ description: 'Lighthouse categories to run (--only-categories). Defaults to the full set, e.g. ["performance"] or ["performance","accessibility","seo"].'
6675
6685
  },
6676
6686
  savePath: {
6677
6687
  type: "string",
@@ -6683,11 +6693,11 @@ var MANIFEST = [
6683
6693
  "desktop",
6684
6694
  "mobile"
6685
6695
  ],
6686
- description: 'Form factor: "desktop" (default, uses --preset=desktop to avoid heavy mobile throttling) or "mobile".'
6696
+ description: '"desktop" (default, uses --preset=desktop to avoid heavy mobile throttling) or "mobile".'
6687
6697
  },
6688
6698
  timeoutMs: {
6689
6699
  type: "number",
6690
- description: "Overall budget for the lighthouse process in milliseconds. Default 120000."
6700
+ description: "Overall budget for the lighthouse process, in ms. Default 120000."
6691
6701
  }
6692
6702
  },
6693
6703
  required: [
@@ -6698,21 +6708,21 @@ var MANIFEST = [
6698
6708
  },
6699
6709
  {
6700
6710
  name: "mock_request",
6701
- description: "Register a mock rule on a target's persistent fake-backend session (CDP Fetch domain): intercept requests whose URL matches urlPattern and fulfill them with a canned response, fail (abort) them, or continue them, optionally with fault injection (delayMs/failRate). The session survives reloads and navigations and lives until clear_mocks. Call repeatedly to mock several endpoints on the same target. Pass reload:true to apply immediately. Persistent across calls only via the MCP server (not the one-shot CLI).",
6711
+ description: "Register a mock rule on a target's persistent fake-backend session (CDP Fetch domain): match requests by urlPattern and fulfill with a canned response, fail (abort), or continue -- optionally with fault injection (delayMs/failRate). The session survives reloads/navigations and lives until clear_mocks; call repeatedly to mock several endpoints on one target. reload:true applies it immediately. Persistent across calls only via the MCP server, not the one-shot CLI.",
6702
6712
  inputSchema: {
6703
6713
  type: "object",
6704
6714
  properties: {
6705
6715
  lease: {
6706
6716
  type: "string",
6707
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6717
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6708
6718
  },
6709
6719
  target: {
6710
6720
  type: "string",
6711
- description: "Target selector: active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Defaults to the active page."
6721
+ description: "Which page's mock session. Defaults to the active page. Grammar: server instructions."
6712
6722
  },
6713
6723
  urlPattern: {
6714
6724
  type: "string",
6715
- description: `CDP Fetch urlPattern glob: '*' matches any run of characters, '?' exactly one, '\\' escapes. Only matching request URLs are paused. e.g. "*/api/users*".`
6725
+ description: `CDP Fetch urlPattern glob: '*' any run of chars, '?' exactly one, '\\' escapes. Only matching URLs are paused. e.g. "*/api/users*".`
6716
6726
  },
6717
6727
  action: {
6718
6728
  type: "string",
@@ -6725,7 +6735,7 @@ var MANIFEST = [
6725
6735
  },
6726
6736
  status: {
6727
6737
  type: "number",
6728
- description: "fulfill: HTTP status code for the canned response. Default 200."
6738
+ description: "fulfill: HTTP status for the canned response. Default 200."
6729
6739
  },
6730
6740
  body: {
6731
6741
  type: "string",
@@ -6745,7 +6755,7 @@ var MANIFEST = [
6745
6755
  },
6746
6756
  method: {
6747
6757
  type: "string",
6748
- description: 'Only mock requests with this HTTP method (e.g. "POST"); other methods pass through.'
6758
+ description: 'Only mock this HTTP method (e.g. "POST"); others pass through.'
6749
6759
  },
6750
6760
  delayMs: {
6751
6761
  type: "number",
@@ -6753,11 +6763,11 @@ var MANIFEST = [
6753
6763
  },
6754
6764
  failRate: {
6755
6765
  type: "number",
6756
- description: "Fault injection: probability 0..1 of failing a matched request regardless of action (resilience testing)."
6766
+ description: "Fault injection: probability 0..1 of failing a matched request regardless of action."
6757
6767
  },
6758
6768
  reload: {
6759
6769
  type: "boolean",
6760
- description: "Reload the target after arming so the mock immediately catches traffic."
6770
+ description: "Reload the target after arming so the mock catches traffic immediately."
6761
6771
  }
6762
6772
  },
6763
6773
  required: [
@@ -6768,17 +6778,17 @@ var MANIFEST = [
6768
6778
  },
6769
6779
  {
6770
6780
  name: "list_mocks",
6771
- description: "List active fake-backend sessions (one per target) with their rules and hit counts. Prunes sessions whose tab has closed. Use to see what is currently being mocked.",
6781
+ description: "List active fake-backend sessions (one per target) with their rules and hit counts. Prunes sessions whose tab has closed.",
6772
6782
  inputSchema: {
6773
6783
  type: "object",
6774
6784
  properties: {
6775
6785
  lease: {
6776
6786
  type: "string",
6777
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6787
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6778
6788
  },
6779
6789
  target: {
6780
6790
  type: "string",
6781
- description: "Unused filter placeholder; list_mocks returns all active sessions."
6791
+ description: "Unused filter placeholder; returns all active sessions."
6782
6792
  }
6783
6793
  },
6784
6794
  required: [],
@@ -6793,11 +6803,11 @@ var MANIFEST = [
6793
6803
  properties: {
6794
6804
  lease: {
6795
6805
  type: "string",
6796
- description: "Opaque lease token from claim_page. Omit it for a tab this process already holds: under CDP_REQUIRE_LEASE the gate acquires a lease automatically and one acquired that way passes for any later call from the same process. Required when the tab is held by ANOTHER process, and required for a tab claimed explicitly via claim_page or new_page{claim:true}, which always demands its own token. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all."
6806
+ description: "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions."
6797
6807
  },
6798
6808
  target: {
6799
6809
  type: "string",
6800
- description: "Target selector whose mock session to clear. Defaults to the active page. Ignored when all:true."
6810
+ description: "Which page's mock session to clear. Defaults to the active page; ignored when all:true. Grammar: server instructions."
6801
6811
  },
6802
6812
  all: {
6803
6813
  type: "boolean",
@@ -6810,29 +6820,29 @@ var MANIFEST = [
6810
6820
  },
6811
6821
  {
6812
6822
  name: "claim_page",
6813
- description: "Take exclusive ownership of a browser tab and return an opaque lease token. Two modes. FRESH TAB: with no target and no targetId, opens a new tab (optionally at url) and claims it, so 'give me my own tab' is one call. TAKEOVER: with target, claims a tab that is already open — this is how you work in a tab the human already has open when they ask you to, instead of opening your own — resolving any target selector against the live page list and never creating anything. The answer's 'opened' flag says which happened (true only when this call created the tab), and that same creation record is what release_page consults, so a tab you took over is left open when you release it. Any later tool call against a leased tab must carry the matching token in its 'lease' argument or it is refused, naming the holder. The lease is reclaimable once its owning process dies, its ttlMs elapses without use, or its tab is closed. Refused from the CLI: a CLI invocation is one process per call, so its lease would be reclaimable immediately. IS SOMEBODY ELSE IN THIS TAB: the answer also carries 'humanActiveMs', milliseconds since input that this server did not dispatch — i.e. since a person last clicked, typed, or scrolled here. null means NO DATA (the tab carries no activity beacon, which is normal the first time anyone claims a person's tab, or every input on it was this server's own); it never means 'no human'. The field is absent entirely on a backend that cannot answer. On a takeover of a tab a human used within the last 30 seconds the answer additionally carries 'contention', a warning string. THE CLAIM IS NEVER REFUSED FOR THIS: taking over a person's tab is what takeover mode is for, so by the time you read the warning you already hold the lease and the tab is yours to drive. It tells you that driving it now means fighting a live person for the keyboard and mouse, so you can open your own tab instead or ask first. Known limits: input inside a cross-origin iframe is invisible to it, and a second MCP server's clicks read as human to this one.",
6823
+ description: "Take exclusive ownership of a browser tab, returning an opaque lease token (needed once under CDP_REQUIRE_LEASE for any tab you drive). Two modes: FRESH TAB (no target - opens one, optionally at url, and claims it: 'give me my own tab' in one call) and TAKEOVER (target - claims an already-open tab, e.g. the human's, resolving the selector against the live page list, creating nothing). The opened flag is true only when this call created the tab, and release_page consults that record, so a taken-over tab is left open on release. Refused from the CLI (its per-call process would make the lease instantly reclaimable). The answer carries humanActiveMs, ms since input this server did not dispatch; null is NO DATA (no activity beacon, or every input was this server's own) and never means 'no human'; absent on a backend that cannot answer. On takeover of a tab a human used within 30s it adds contention, a warning - but THE CLAIM IS NEVER REFUSED FOR THIS: takeover is its purpose, you already hold the lease; it only warns you're fighting a live person, so open your own tab or ask first. Limits: input inside a cross-origin iframe is invisible, and a second MCP server's clicks read as human.",
6814
6824
  inputSchema: {
6815
6825
  type: "object",
6816
6826
  properties: {
6817
6827
  target: {
6818
6828
  type: "string",
6819
- description: "Take over an already-open tab, e.g. one the human has open, when asked to: pass any target selector — active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Resolved against the live page list only, so it NEVER opens a tab: a selector that matches nothing is an error, not a new blank tab. The tab is left open on release because the toolkit did not create it. Refused if another live process holds it, including a lease the gate acquired for that process automatically under CDP_REQUIRE_LEASE: this takes over unleased (human) tabs and never steals one from a live agent. Mutually exclusive with targetId."
6829
+ description: "Take over an already-open tab (e.g. one the human has open). Resolved against the LIVE page list only, so it never opens a tab — a selector matching nothing is an error. Left open on release. Refused if another live process holds it (including a lease auto-acquired under CDP_REQUIRE_LEASE): takes over unleased human tabs, never steals from a live agent. Mutually exclusive with targetId. Selector grammar: server instructions."
6820
6830
  },
6821
6831
  targetId: {
6822
6832
  type: "string",
6823
- description: "Claim this already-open page target instead of opening a new tab. Must be an exact target id, not a selector. Kept for back-compat: prefer 'target', which accepts an exact id too plus every other selector form. Mutually exclusive with target."
6833
+ description: "Exact target id (not a selector) of an already-open page to claim instead of opening one. Back-compat only; prefer target, which also accepts an exact id. Mutually exclusive with target."
6824
6834
  },
6825
6835
  url: {
6826
6836
  type: "string",
6827
- description: "When opening a fresh tab, navigate it here. Ignored when target or targetId is given, since both of those claim a tab that is already open."
6837
+ description: "When opening a fresh tab, navigate it here. Ignored when target or targetId is given."
6828
6838
  },
6829
6839
  label: {
6830
6840
  type: "string",
6831
- description: "Agent label recorded on the lease and surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
6841
+ description: "Agent label recorded on the lease; surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
6832
6842
  },
6833
6843
  ttlMs: {
6834
6844
  type: "number",
6835
- description: "How long the lease survives without use before it is reclaimable. Defaults to CDP_LEASE_TTL_MS, else 900000 (15 minutes). Every checked call refreshes it, so an active agent never expires."
6845
+ description: "How long the lease survives without use before it is reclaimable. Defaults to CDP_LEASE_TTL_MS, else 900000 (15min); every checked call refreshes it."
6836
6846
  }
6837
6847
  },
6838
6848
  required: [],
@@ -6841,21 +6851,21 @@ var MANIFEST = [
6841
6851
  },
6842
6852
  {
6843
6853
  name: "release_page",
6844
- description: "Give a lease back, and close the tab if this toolkit opened it. Takes exactly one of 'lease' (the token claim_page returned) or 'target' (a selector for a tab this process holds, which is how you release a lease the gate acquired for you automatically, since that path never hands you a token). A tab with a creation record from this toolkit is closed; a tab that was already open and merely claimed is released and left alone. Override either way with 'close'. Idempotent: an already-released, reclaimed, or expired lease reports released:false, and a release that did not happen never closes anything, because by then the tab may belong to another agent. Answers {released, closed, targetId}.",
6854
+ description: "Give a lease back, closing the tab only if this toolkit opened it. Take exactly one of lease (the token) or target (a selector for a tab this process holds - use this to release a lease the gate acquired for you automatically, since that path never handed you a token). A tab with a creation record from this toolkit is closed; a tab that was already open and merely claimed is released and left alone - override either way with close. Idempotent: an already-released, reclaimed, or expired lease reports released:false and closes nothing (by then the tab may belong to another agent). Answers {released, closed, targetId}.",
6845
6855
  inputSchema: {
6846
6856
  type: "object",
6847
6857
  properties: {
6848
6858
  lease: {
6849
6859
  type: "string",
6850
- description: "The opaque token claim_page (or new_page with claim:true) returned. Mutually exclusive with 'target'. Under CDP_REQUIRE_LEASE a lease the gate acquired for you automatically never produced a token in the first place, so give 'target' instead."
6860
+ description: "Token from claim_page / new_page{claim:true}. Mutually exclusive with 'target'. Under CDP_REQUIRE_LEASE an auto-acquired lease never produced a token — give 'target' instead."
6851
6861
  },
6852
6862
  target: {
6853
6863
  type: "string",
6854
- description: "Target selector for a tab this process holds: active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Mutually exclusive with 'lease'. Refused if another process holds the tab."
6864
+ description: "A tab THIS process holds (grammar: server instructions). Mutually exclusive with 'lease'. Refused if another process holds the tab."
6855
6865
  },
6856
6866
  close: {
6857
6867
  type: "boolean",
6858
- description: "Force the close decision instead of letting the creation ledger decide. true closes even a tab this toolkit did not open; false keeps an agent-created tab open. Omit for the default, which closes only tabs this toolkit created."
6868
+ description: "Force the close decision instead of letting the creation record decide: true closes even a tab this toolkit did not open; false keeps an agent-created tab open. Omit to close only tabs this toolkit created."
6859
6869
  }
6860
6870
  },
6861
6871
  required: [],
@@ -6864,7 +6874,7 @@ var MANIFEST = [
6864
6874
  },
6865
6875
  {
6866
6876
  name: "list_leases",
6867
- description: "Enumerate every active lease for diagnosis: backend, target id, agent label, pid, createdAt, lastUsedAt, ttlMs, whether the owning process is still alive, and whether the lease is reclaimable and why. Requires no token. Never returns the lease nonce, so this cannot be used to forge a token. A row for a lease file that could not be read or parsed instead carries `unreadable` (the errno, or \"unparseable\"), with label, pid, and the timestamp fields as zero placeholders and stale always false, since stale is what marks a lease free to take and an unreadable one must never read that way. Under CDP_REQUIRE_LEASE this call reaps abandoned agent tabs exactly as list_pages does (see that tool's description for the grace period between a lease reading reclaimable and its tab actually being closed), reporting them in an additive 'reaped' array and omitting their rows from the listing. A row also carries 'humanActiveMs' — milliseconds since input this server did not dispatch, i.e. since a person last used that tab — WHERE THE TAB CAN ANSWER. The field is ABSENT rather than null when it cannot: no activity beacon on that tab, every input on it accounted for by this server, a row belonging to the other backend, a tab no longer open, or a page that would not evaluate. Absence therefore means 'no answer', never 'nobody is there'. Note that lastUsedAt measures TOOLKIT calls only, so a tab a person has been typing in for ten minutes looks perfectly idle by that field and is exactly the case humanActiveMs exists to expose. Two further fields are computed fresh on every call, never read off disk: 'idleMs' (now minus lastUsedAt) and 'expiresAt' (lastUsedAt plus ttlMs — the same value claim_page already returns under that name). Both are omitted on an 'unreadable' row, whose lastUsedAt and ttlMs are the zeroed placeholders described above.",
6877
+ description: "Enumerate every active lease for diagnosis - backend, target id, label, pid, createdAt, lastUsedAt, ttlMs, whether the owning process is alive, and whether the lease is reclaimable and why. No token required; it never returns the lease nonce, so it cannot be used to forge one. An unreadable or unparseable lease row instead carries unreadable, with label, pid and timestamps zeroed and stale forced false (an unreadable lease must never read as free to take). Under CDP_REQUIRE_LEASE it reaps abandoned agent tabs exactly as list_pages does, into an additive reaped array, omitting their rows. Where the tab can answer, a row also carries humanActiveMs, ms since input this server did not dispatch - note lastUsedAt tracks TOOLKIT calls only, so a tab a person has typed in for ten minutes looks idle, which is what this exposes; the field is absent, never null, when the tab cannot answer, and that absence means 'no answer', never 'nobody is there'. idleMs (now - lastUsedAt) and expiresAt (lastUsedAt + ttlMs) are computed fresh each call and omitted on an unreadable row.",
6868
6878
  inputSchema: {
6869
6879
  type: "object",
6870
6880
  properties: {},
@@ -7071,6 +7081,252 @@ async function connectBidiSessionUrl(wsUrl, opts = {}) {
7071
7081
  return conn;
7072
7082
  }
7073
7083
 
7084
+ // src/bidi/session-lease.ts
7085
+ import { mkdir as mkdir12, readFile as readFile8, unlink as unlink5, writeFile as writeFile9 } from "node:fs/promises";
7086
+ import { join as join10 } from "node:path";
7087
+ var DEFAULT_SESSION_WAIT_MS = 1e4;
7088
+ var DEFAULT_POLL_MS = 250;
7089
+ function safeEndpoint(endpoint) {
7090
+ return endpoint.replace(/[^A-Za-z0-9._-]/g, "_");
7091
+ }
7092
+ function sessionSlotFile(endpoint) {
7093
+ return join10(leaseDir(), `ff-session-${safeEndpoint(endpoint)}.json`);
7094
+ }
7095
+ function sessionWaitMs() {
7096
+ const raw = Number(process.env.CDP_FIREFOX_SESSION_WAIT_MS);
7097
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_SESSION_WAIT_MS;
7098
+ }
7099
+
7100
+ class SessionSlotBusyError extends Error {
7101
+ endpoint;
7102
+ holderPid;
7103
+ holderLabel;
7104
+ constructor(endpoint, holder) {
7105
+ const who = holder ? `'${holder.label}' (pid ${holder.pid})` : "another live process";
7106
+ super(`Firefox BiDi session slot for endpoint '${endpoint}' is held by ${who}. ` + `Firefox serves exactly ONE WebDriver-BiDi session per browser instance, and this slot is held ` + `by a LIVE process, so it cannot be taken. Wait for that process to release it and retry, or point ` + `this server at a different endpoint/browser.`);
7107
+ this.name = "SessionSlotBusyError";
7108
+ this.endpoint = endpoint;
7109
+ if (holder) {
7110
+ this.holderPid = holder.pid;
7111
+ this.holderLabel = holder.label;
7112
+ }
7113
+ }
7114
+ }
7115
+ async function readSessionSlot(endpoint) {
7116
+ let raw;
7117
+ try {
7118
+ raw = await readFile8(sessionSlotFile(endpoint), "utf8");
7119
+ } catch (err) {
7120
+ if (err.code === "ENOENT")
7121
+ return;
7122
+ throw err;
7123
+ }
7124
+ try {
7125
+ const rec = JSON.parse(raw);
7126
+ if (typeof rec?.pid === "number" && typeof rec?.endpoint === "string" && typeof rec?.label === "string") {
7127
+ return rec;
7128
+ }
7129
+ } catch {}
7130
+ return;
7131
+ }
7132
+ async function acquireSessionSlot(endpoint, opts) {
7133
+ const waitMs = opts.waitMs ?? sessionWaitMs();
7134
+ const pollMs = opts.pollMs !== undefined && opts.pollMs > 0 ? opts.pollMs : DEFAULT_POLL_MS;
7135
+ const createdAt = opts.now ?? Date.now();
7136
+ const file = sessionSlotFile(endpoint);
7137
+ const record2 = {
7138
+ endpoint,
7139
+ pid: process.pid,
7140
+ label: opts.label,
7141
+ createdAt
7142
+ };
7143
+ if (opts.marionettePort !== undefined)
7144
+ record2.marionettePort = opts.marionettePort;
7145
+ const handle = { endpoint, pid: process.pid, createdAt };
7146
+ const payload = JSON.stringify(record2);
7147
+ await mkdir12(leaseDir(), { recursive: true });
7148
+ const deadline = Date.now() + waitMs;
7149
+ const maxAttempts = Math.max(100, Math.ceil(waitMs / pollMs) + 100);
7150
+ let attempts = 0;
7151
+ let lastLiveHolder;
7152
+ while (true) {
7153
+ if (attempts >= maxAttempts)
7154
+ throw new SessionSlotBusyError(endpoint, lastLiveHolder);
7155
+ attempts++;
7156
+ try {
7157
+ await writeFile9(file, payload, { flag: "wx" });
7158
+ return { handle };
7159
+ } catch (err) {
7160
+ if (err.code !== "EEXIST")
7161
+ throw err;
7162
+ }
7163
+ const holder = await readSessionSlot(endpoint);
7164
+ if (!holder)
7165
+ continue;
7166
+ if (!isPidAlive(holder.pid)) {
7167
+ await unlink5(file).catch(() => {
7168
+ return;
7169
+ });
7170
+ try {
7171
+ await writeFile9(file, payload, { flag: "wx" });
7172
+ return { handle, staleHolder: holder };
7173
+ } catch (err) {
7174
+ if (err.code !== "EEXIST")
7175
+ throw err;
7176
+ continue;
7177
+ }
7178
+ }
7179
+ lastLiveHolder = holder;
7180
+ const remaining = deadline - Date.now();
7181
+ if (remaining <= 0)
7182
+ throw new SessionSlotBusyError(endpoint, holder);
7183
+ await sleep2(Math.min(pollMs, remaining));
7184
+ }
7185
+ }
7186
+ async function releaseSessionSlot(handle) {
7187
+ const current = await readSessionSlot(handle.endpoint);
7188
+ if (!current)
7189
+ return { released: false };
7190
+ if (current.pid !== process.pid || current.createdAt !== handle.createdAt)
7191
+ return { released: false };
7192
+ await unlink5(sessionSlotFile(handle.endpoint)).catch(() => {
7193
+ return;
7194
+ });
7195
+ return { released: true };
7196
+ }
7197
+ function sleep2(ms) {
7198
+ return new Promise((resolve) => setTimeout(resolve, ms));
7199
+ }
7200
+
7201
+ // src/bidi/marionette.ts
7202
+ import { Socket } from "node:net";
7203
+ var DEFAULT_MARIONETTE_PORT = 2828;
7204
+ var DEFAULT_TIMEOUT_MS3 = 4000;
7205
+ var HOST = "127.0.0.1";
7206
+ var COLON = 58;
7207
+ var DELETE_SESSION_FRAME = encodeFrame([0, 1, "WebDriver:DeleteSession", {}]);
7208
+ function defaultMarionettePort() {
7209
+ const raw = Number(process.env.CDP_FIREFOX_MARIONETTE_PORT);
7210
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MARIONETTE_PORT;
7211
+ }
7212
+ function encodeFrame(json) {
7213
+ const body = Buffer.from(JSON.stringify(json), "utf8");
7214
+ const prefix = Buffer.from(`${body.length}:`, "ascii");
7215
+ return Buffer.concat([prefix, body]);
7216
+ }
7217
+
7218
+ class MarionetteProtocolError extends Error {
7219
+ constructor(message) {
7220
+ super(message);
7221
+ this.name = "MarionetteProtocolError";
7222
+ }
7223
+ }
7224
+
7225
+ class FrameDecoder {
7226
+ buf = Buffer.alloc(0);
7227
+ push(chunk) {
7228
+ this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);
7229
+ }
7230
+ next() {
7231
+ const colon = this.buf.indexOf(COLON);
7232
+ if (colon === -1) {
7233
+ this.assertDigitsPrefix(this.buf.length);
7234
+ return;
7235
+ }
7236
+ if (colon === 0)
7237
+ throw new MarionetteProtocolError("empty frame length prefix");
7238
+ this.assertDigitsPrefix(colon);
7239
+ const len = Number.parseInt(this.buf.toString("ascii", 0, colon), 10);
7240
+ const start = colon + 1;
7241
+ const end = start + len;
7242
+ if (this.buf.length < end)
7243
+ return;
7244
+ const payload = this.buf.toString("utf8", start, end);
7245
+ this.buf = this.buf.subarray(end);
7246
+ try {
7247
+ return JSON.parse(payload);
7248
+ } catch {
7249
+ throw new MarionetteProtocolError(`frame body was not valid JSON (${len} bytes): ${payload.slice(0, 80)}`);
7250
+ }
7251
+ }
7252
+ assertDigitsPrefix(upTo) {
7253
+ for (let i = 0;i < upTo; i++) {
7254
+ const b = this.buf[i];
7255
+ if (b < 48 || b > 57) {
7256
+ throw new MarionetteProtocolError(`non-digit byte 0x${b.toString(16)} in frame length prefix`);
7257
+ }
7258
+ }
7259
+ }
7260
+ }
7261
+ function isSlotFreedReply(frame) {
7262
+ if (!Array.isArray(frame))
7263
+ return false;
7264
+ const err = frame[2];
7265
+ if (err === null)
7266
+ return true;
7267
+ if (typeof err === "object" && err !== null && err.error === "invalid session id") {
7268
+ return true;
7269
+ }
7270
+ return false;
7271
+ }
7272
+ function forceClearOrphanSession(marionettePort, opts = {}) {
7273
+ const timeoutMs = opts.timeoutMs !== undefined && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0 ? opts.timeoutMs : DEFAULT_TIMEOUT_MS3;
7274
+ return new Promise((resolve) => {
7275
+ if (!Number.isFinite(marionettePort) || marionettePort <= 0 || marionettePort > 65535) {
7276
+ resolve(false);
7277
+ return;
7278
+ }
7279
+ const sock = new Socket;
7280
+ const decoder = new FrameDecoder;
7281
+ let settled = false;
7282
+ let handshakeSeen = false;
7283
+ let commandSent = false;
7284
+ const settle = (value) => {
7285
+ if (settled)
7286
+ return;
7287
+ settled = true;
7288
+ clearTimeout(timer);
7289
+ sock.removeAllListeners();
7290
+ sock.destroy();
7291
+ resolve(value);
7292
+ };
7293
+ const timer = setTimeout(() => settle(false), timeoutMs);
7294
+ if (typeof timer.unref === "function")
7295
+ timer.unref();
7296
+ sock.on("error", () => settle(false));
7297
+ sock.on("close", () => settle(false));
7298
+ sock.on("data", (chunk) => {
7299
+ if (settled)
7300
+ return;
7301
+ try {
7302
+ decoder.push(chunk);
7303
+ for (;; ) {
7304
+ const frame = decoder.next();
7305
+ if (frame === undefined)
7306
+ break;
7307
+ if (!handshakeSeen) {
7308
+ handshakeSeen = true;
7309
+ continue;
7310
+ }
7311
+ settle(isSlotFreedReply(frame));
7312
+ return;
7313
+ }
7314
+ if (handshakeSeen && !commandSent) {
7315
+ commandSent = true;
7316
+ sock.write(DELETE_SESSION_FRAME);
7317
+ }
7318
+ } catch {
7319
+ settle(false);
7320
+ }
7321
+ });
7322
+ try {
7323
+ sock.connect(marionettePort, HOST);
7324
+ } catch {
7325
+ settle(false);
7326
+ }
7327
+ });
7328
+ }
7329
+
7074
7330
  // src/bidi/snapshot.ts
7075
7331
  var STAMP_ATTR = "data-cdp-uid";
7076
7332
  async function takeStampedSnapshot(evaluate) {
@@ -7647,6 +7903,22 @@ var BIDI_CAPABILITIES = new Set([
7647
7903
  "locate.xpath"
7648
7904
  ]);
7649
7905
  var connections = new Map;
7906
+ var slotHandles = new Map;
7907
+ var connecting = new Map;
7908
+ async function releaseSlotOnDialFailure(endpoint, owned) {
7909
+ if (owned)
7910
+ return;
7911
+ const h = slotHandles.get(endpoint);
7912
+ if (h) {
7913
+ await releaseSessionSlot(h).catch(() => {
7914
+ return;
7915
+ });
7916
+ slotHandles.delete(endpoint);
7917
+ }
7918
+ }
7919
+ function orphanNotClearedError(endpoint) {
7920
+ return driverError2("disconnected", `Firefox holds an orphaned WebDriver BiDi session on ${endpoint} that could not be auto-cleared. ` + `Marionette recovery needs Firefox launched with --marionette (default port 2828, or set ` + `CDP_FIREFOX_MARIONETTE_PORT); otherwise restart Firefox to free the slot. (A client killed without ` + `session.end can leave a session Firefox does not reap.)`);
7921
+ }
7650
7922
  async function getConnection(endpoint, timeoutMs) {
7651
7923
  const existing = connections.get(endpoint);
7652
7924
  if (existing && existing.isOpen) {
@@ -7655,17 +7927,68 @@ async function getConnection(endpoint, timeoutMs) {
7655
7927
  }
7656
7928
  if (existing)
7657
7929
  connections.delete(endpoint);
7658
- const conn = await connectBidiSessionUrl(endpoint, {
7659
- ...timeoutMs !== undefined ? { timeoutMs } : {},
7660
- capabilities: { alwaysMatch: { unhandledPromptBehavior: { default: "ignore" } } }
7661
- }).catch((e) => {
7662
- if (e instanceof BidiError && /maximum number of active sessions/i.test(e.message)) {
7663
- throw driverError2("disconnected", `Firefox already has an active WebDriver BiDi session on ${endpoint}; it allows only one. ` + `Close the other client, or restart Firefox, then retry. (A previously killed client can leave a ` + `session that Firefox does not reap.)`);
7930
+ const inflight = connecting.get(endpoint);
7931
+ if (inflight) {
7932
+ const conn = await inflight;
7933
+ conn.retain();
7934
+ return conn;
7935
+ }
7936
+ const create = (async () => {
7937
+ const mport = defaultMarionettePort();
7938
+ const owned = slotHandles.has(endpoint);
7939
+ if (!owned) {
7940
+ let acq;
7941
+ try {
7942
+ acq = await acquireSessionSlot(endpoint, { label: defaultLabel(), marionettePort: mport });
7943
+ } catch (e) {
7944
+ if (e instanceof SessionSlotBusyError) {
7945
+ const who = e.holderLabel ? `'${e.holderLabel}' (pid ${e.holderPid})` : "another live process";
7946
+ throw driverError2("disconnected", `Firefox's single WebDriver BiDi session on ${endpoint} is held by a LIVE process ${who}. ` + `Firefox serves exactly ONE BiDi session per browser instance, so this server cannot take it ` + `while that process is running. Wait for it to release the session and retry, or point this ` + `server at a different endpoint/browser.`);
7947
+ }
7948
+ throw e;
7949
+ }
7950
+ slotHandles.set(endpoint, acq.handle);
7951
+ if (acq.staleHolder) {
7952
+ await forceClearOrphanSession(acq.staleHolder.marionettePort ?? mport);
7953
+ }
7664
7954
  }
7665
- throw e;
7666
- });
7667
- connections.set(endpoint, conn);
7668
- return conn;
7955
+ const dialOpts = {
7956
+ ...timeoutMs !== undefined ? { timeoutMs } : {},
7957
+ capabilities: { alwaysMatch: { unhandledPromptBehavior: { default: "ignore" } } }
7958
+ };
7959
+ let conn;
7960
+ try {
7961
+ conn = await connectBidiSessionUrl(endpoint, dialOpts);
7962
+ } catch (e) {
7963
+ if (e instanceof BidiError && /maximum number of active sessions/i.test(e.message)) {
7964
+ const cleared = await forceClearOrphanSession(mport);
7965
+ if (!cleared) {
7966
+ await releaseSlotOnDialFailure(endpoint, owned);
7967
+ throw orphanNotClearedError(endpoint);
7968
+ }
7969
+ try {
7970
+ conn = await connectBidiSessionUrl(endpoint, dialOpts);
7971
+ } catch (e2) {
7972
+ await releaseSlotOnDialFailure(endpoint, owned);
7973
+ if (e2 instanceof BidiError && /maximum number of active sessions/i.test(e2.message)) {
7974
+ throw orphanNotClearedError(endpoint);
7975
+ }
7976
+ throw e2;
7977
+ }
7978
+ } else {
7979
+ await releaseSlotOnDialFailure(endpoint, owned);
7980
+ throw e;
7981
+ }
7982
+ }
7983
+ connections.set(endpoint, conn);
7984
+ return conn;
7985
+ })();
7986
+ connecting.set(endpoint, create);
7987
+ try {
7988
+ return await create;
7989
+ } finally {
7990
+ connecting.delete(endpoint);
7991
+ }
7669
7992
  }
7670
7993
  var bidiBeaconScripts = new Map;
7671
7994
  async function safeTitle(conn, contextId) {
@@ -8431,14 +8754,27 @@ class BidiBrowserDriver {
8431
8754
  if (key.startsWith(`${this.endpoint}:`))
8432
8755
  bidiBeaconScripts.delete(key);
8433
8756
  }
8757
+ connecting.delete(this.endpoint);
8434
8758
  const conn = connections.get(this.endpoint);
8435
- if (!conn)
8759
+ if (!conn) {
8760
+ await this.releaseSessionSlotIfHeld();
8436
8761
  return;
8762
+ }
8437
8763
  connections.delete(this.endpoint);
8438
8764
  await conn.send("session.end", {}).catch(() => {
8439
8765
  return;
8440
8766
  });
8441
8767
  conn.dispose();
8768
+ await this.releaseSessionSlotIfHeld();
8769
+ }
8770
+ async releaseSessionSlotIfHeld() {
8771
+ const h = slotHandles.get(this.endpoint);
8772
+ if (!h)
8773
+ return;
8774
+ await releaseSessionSlot(h).catch(() => {
8775
+ return;
8776
+ });
8777
+ slotHandles.delete(this.endpoint);
8442
8778
  }
8443
8779
  }
8444
8780
  function createFirefoxDriver(port, opts) {
@@ -8453,7 +8789,7 @@ import { spawn as spawn3 } from "node:child_process";
8453
8789
  import { existsSync, mkdtempSync, rmSync } from "node:fs";
8454
8790
  import { createConnection, createServer } from "node:net";
8455
8791
  import { tmpdir } from "node:os";
8456
- import { join as join10 } from "node:path";
8792
+ import { join as join11 } from "node:path";
8457
8793
  function resolveBinary() {
8458
8794
  const candidates = [
8459
8795
  process.env.FIREFOX_BIN,
@@ -8531,7 +8867,7 @@ async function launchFirefox(opts = {}) {
8531
8867
  const binary = resolveBinary();
8532
8868
  const port = await pickPort(opts.port);
8533
8869
  const isThrowaway = opts.profilePath === undefined;
8534
- const profilePath = opts.profilePath ?? mkdtempSync(join10(tmpdir(), "cdp-toolkit-ff-profile-"));
8870
+ const profilePath = opts.profilePath ?? mkdtempSync(join11(tmpdir(), "cdp-toolkit-ff-profile-"));
8535
8871
  const args = ["--profile", profilePath, "--remote-debugging-port", String(port), "--no-remote"];
8536
8872
  if (opts.headless)
8537
8873
  args.push("--headless");
@@ -8882,7 +9218,7 @@ var FIREFOX_TOOLS = {
8882
9218
  };
8883
9219
 
8884
9220
  // src/mcp.ts
8885
- var VERSION = "1.9.7";
9221
+ var VERSION = "1.11.0";
8886
9222
  var { kind: BROWSER, endpoint: FIREFOX_ENDPOINT } = resolveBackend(process.argv.slice(2));
8887
9223
  var AVAILABILITY = toolAvailability(BROWSER);
8888
9224
  var AVAILABLE_NAMES = new Set(AVAILABILITY.available);
@@ -8898,7 +9234,15 @@ function auditCoverage() {
8898
9234
  if (orphanSchema.length)
8899
9235
  console.error(`[cdp-toolkit] WARN: manifest schemas with no registered tool: ${orphanSchema.join(", ")}`);
8900
9236
  }
8901
- var server = new Server({ name: "cdp-toolkit", version: VERSION }, { capabilities: { tools: {} } });
9237
+ var INSTRUCTIONS = [
9238
+ "cdp-toolkit drives a real browser over Chrome DevTools Protocol (or Firefox WebDriver-BiDi): open and drive pages, click/fill/type, screenshot and accessibility-snapshot, read console + network, set cookies, emulate devices, run Lighthouse and performance traces, record screencasts. Conventions shared across its tools:",
9239
+ "TARGET SELECTOR (the `target` param, unless a tool says otherwise): 'active' (default = first page) | 'index:N' (0-based) | 'url:<substr>' | 'title:<substr>' | 'label:<name>' (exact, from the origin ledger or a live lease) | a 32-hex '<targetId>'. Four Chrome-only tools (evaluate_script, list_network_requests, get_network_request, list_console_messages) also accept 'worker:<substr>' to reach a service/shared worker — e.g. an MV3 extension's background worker (worker:<extension-id> or worker:background.js); an idle-evicted worker is started first (see each tool's `wake`).",
9240
+ "LEASES (the `lease` param): claim_page, and new_page{claim:true}, mint an opaque token. Omit it for a tab THIS process already holds. It is required for a tab held by ANOTHER process, or one claimed explicitly. Under CDP_REQUIRE_LEASE the gate auto-acquires a lease for any tab this process drives — no token is surfaced, so pass `target`, not `lease` — while an explicit claim:true still demands its token on every later call. Without CDP_REQUIRE_LEASE an unleased tab needs no token at all.",
9241
+ "ORIGIN: list_pages and list_leases tag each tab's `origin` as 'agent' (this toolkit created it) or 'unknown' — never 'human', because the toolkit cannot prove a person opened a tab. An 'agent' tab stays findable after its creator releases the lease or dies."
9242
+ ].join(`
9243
+
9244
+ `);
9245
+ var server = new Server({ name: "cdp-toolkit", version: VERSION }, { capabilities: { tools: {} }, instructions: INSTRUCTIONS });
8902
9246
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
8903
9247
  tools: MANIFEST.filter((s) => AVAILABLE_NAMES.has(s.name)).map((s) => ({ name: s.name, description: s.description, inputSchema: s.inputSchema }))
8904
9248
  }));