cdp-toolkit 1.6.0 → 1.8.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/src/manifest.ts CHANGED
@@ -25,17 +25,21 @@ export interface ToolSpec {
25
25
  export const MANIFEST: ToolSpec[] = [
26
26
  {
27
27
  "name": "list_pages",
28
- "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.",
28
+ "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.",
29
29
  "inputSchema": {
30
30
  "type": "object",
31
31
  "properties": {
32
32
  "lease": {
33
33
  "type": "string",
34
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
34
+ "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."
35
35
  },
36
36
  "all": {
37
37
  "type": "boolean",
38
38
  "description": "Include non-page targets (service/shared workers, background pages) when true; otherwise only page-type tabs are listed."
39
+ },
40
+ "probe": {
41
+ "type": "boolean",
42
+ "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."
39
43
  }
40
44
  },
41
45
  "required": [],
@@ -50,7 +54,7 @@ export const MANIFEST: ToolSpec[] = [
50
54
  "properties": {
51
55
  "lease": {
52
56
  "type": "string",
53
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
57
+ "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."
54
58
  },
55
59
  "url": {
56
60
  "type": "string",
@@ -58,7 +62,7 @@ export const MANIFEST: ToolSpec[] = [
58
62
  },
59
63
  "claim": {
60
64
  "type": "boolean",
61
- "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."
65
+ "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."
62
66
  },
63
67
  "label": {
64
68
  "type": "string",
@@ -81,7 +85,7 @@ export const MANIFEST: ToolSpec[] = [
81
85
  "properties": {
82
86
  "lease": {
83
87
  "type": "string",
84
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
88
+ "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."
85
89
  },
86
90
  "target": {
87
91
  "type": "string",
@@ -102,7 +106,7 @@ export const MANIFEST: ToolSpec[] = [
102
106
  "properties": {
103
107
  "lease": {
104
108
  "type": "string",
105
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
109
+ "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."
106
110
  },
107
111
  "target": {
108
112
  "type": "string",
@@ -117,13 +121,13 @@ export const MANIFEST: ToolSpec[] = [
117
121
  },
118
122
  {
119
123
  "name": "navigate_page",
120
- "description": "Navigate a target page to a URL (Page.navigate) OR reload it (reload:true) over raw CDP, then wait for the load milestone with a bounded timeout so a wedged renderer can't hang. Returns {url,frameId,waitedFor} (plus reloaded:true on a reload); waitUntil supports 'load'|'domcontentloaded' only (no 'networkidle'), and there is no auto-snapshot of the new page. Pass reload:true with ignoreCache:true for a hard reload that refetches every subresource (e.g. to pick up a freshly-deployed, non-content-hashed bundle the HTTP cache would serve stale).",
124
+ "description": "Navigate a target page to a URL (Page.navigate), reload it (reload:true), OR go back/forward in its session history (history:'back'|'forward'), then wait for the load milestone with a bounded timeout so a wedged renderer can't hang. Exactly one of url / reload / history per call. Returns {url,frameId,waitedFor} (plus reloaded:true on a reload, traversed:'back'|'forward' on a history move); waitUntil supports 'load'|'domcontentloaded' only (no 'networkidle'), and there is no auto-snapshot of the new page. Pass reload:true with ignoreCache:true for a hard reload that refetches every subresource (e.g. to pick up a freshly-deployed, non-content-hashed bundle the HTTP cache would serve stale).",
121
125
  "inputSchema": {
122
126
  "type": "object",
123
127
  "properties": {
124
128
  "lease": {
125
129
  "type": "string",
126
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
130
+ "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."
127
131
  },
128
132
  "target": {
129
133
  "type": "string",
@@ -137,6 +141,14 @@ export const MANIFEST: ToolSpec[] = [
137
141
  "type": "boolean",
138
142
  "description": "Reload the current page (Page.reload) instead of navigating to url. Default false."
139
143
  },
144
+ "history": {
145
+ "type": "string",
146
+ "enum": [
147
+ "back",
148
+ "forward"
149
+ ],
150
+ "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."
151
+ },
140
152
  "ignoreCache": {
141
153
  "type": "boolean",
142
154
  "description": "On reload, bypass the HTTP cache (hard reload) so subresources are refetched. Ignored unless reload:true. Default false."
@@ -165,7 +177,7 @@ export const MANIFEST: ToolSpec[] = [
165
177
  "properties": {
166
178
  "lease": {
167
179
  "type": "string",
168
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
180
+ "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."
169
181
  },
170
182
  "target": {
171
183
  "type": "string",
@@ -198,7 +210,7 @@ export const MANIFEST: ToolSpec[] = [
198
210
  "properties": {
199
211
  "lease": {
200
212
  "type": "string",
201
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
213
+ "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."
202
214
  },
203
215
  "target": {
204
216
  "type": "string",
@@ -235,7 +247,7 @@ export const MANIFEST: ToolSpec[] = [
235
247
  "properties": {
236
248
  "lease": {
237
249
  "type": "string",
238
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs."
250
+ "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."
239
251
  },
240
252
  "target": {
241
253
  "type": "string",
@@ -266,7 +278,7 @@ export const MANIFEST: ToolSpec[] = [
266
278
  "properties": {
267
279
  "lease": {
268
280
  "type": "string",
269
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs."
281
+ "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."
270
282
  },
271
283
  "target": {
272
284
  "type": "string",
@@ -322,7 +334,7 @@ export const MANIFEST: ToolSpec[] = [
322
334
  "properties": {
323
335
  "lease": {
324
336
  "type": "string",
325
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs."
337
+ "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."
326
338
  },
327
339
  "target": {
328
340
  "type": "string",
@@ -357,7 +369,7 @@ export const MANIFEST: ToolSpec[] = [
357
369
  "properties": {
358
370
  "lease": {
359
371
  "type": "string",
360
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
372
+ "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."
361
373
  },
362
374
  "target": {
363
375
  "type": "string",
@@ -374,13 +386,13 @@ export const MANIFEST: ToolSpec[] = [
374
386
  },
375
387
  {
376
388
  "name": "click",
377
- "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).",
389
+ "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.",
378
390
  "inputSchema": {
379
391
  "type": "object",
380
392
  "properties": {
381
393
  "lease": {
382
394
  "type": "string",
383
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
395
+ "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."
384
396
  },
385
397
  "target": {
386
398
  "type": "string",
@@ -405,7 +417,20 @@ export const MANIFEST: ToolSpec[] = [
405
417
  },
406
418
  "clickCount": {
407
419
  "type": "number",
408
- "description": "Number of clicks: 1 = single (default), 2 = double-click."
420
+ "description": "Number of clicks: 1 = single (default), 2 = double-click, 3 = triple-click."
421
+ },
422
+ "modifiers": {
423
+ "type": "array",
424
+ "items": {
425
+ "type": "string",
426
+ "enum": [
427
+ "Alt",
428
+ "Control",
429
+ "Meta",
430
+ "Shift"
431
+ ]
432
+ },
433
+ "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."
409
434
  }
410
435
  },
411
436
  "required": [],
@@ -420,7 +445,7 @@ export const MANIFEST: ToolSpec[] = [
420
445
  "properties": {
421
446
  "lease": {
422
447
  "type": "string",
423
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
448
+ "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."
424
449
  },
425
450
  "target": {
426
451
  "type": "string",
@@ -441,13 +466,13 @@ export const MANIFEST: ToolSpec[] = [
441
466
  },
442
467
  {
443
468
  "name": "drag",
444
- "description": "Drag from a source element to a destination element via synthetic mouse press/move/release (approximates HTML5 drag-and-drop; not guaranteed for every custom DnD library). 'from' and 'to' each take exactly one of uid (a CDP backendDOMNodeId from take_snapshot) or selector.",
469
+ "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.",
445
470
  "inputSchema": {
446
471
  "type": "object",
447
472
  "properties": {
448
473
  "lease": {
449
474
  "type": "string",
450
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
475
+ "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."
451
476
  },
452
477
  "target": {
453
478
  "type": "string",
@@ -470,7 +495,7 @@ export const MANIFEST: ToolSpec[] = [
470
495
  },
471
496
  "to": {
472
497
  "type": "object",
473
- "description": "Destination element to drag to. Provide exactly one of uid or selector.",
498
+ "description": "Where the drag ends: exactly one of uid, selector, or x+y. Mutually exclusive with 'by'; exactly one of to/by is required.",
474
499
  "properties": {
475
500
  "uid": {
476
501
  "type": "number",
@@ -479,18 +504,222 @@ export const MANIFEST: ToolSpec[] = [
479
504
  "selector": {
480
505
  "type": "string",
481
506
  "description": "CSS selector for the destination element (resolved via document.querySelector)."
507
+ },
508
+ "x": {
509
+ "type": "number",
510
+ "description": "Absolute viewport x-coordinate to drop at. Must be given together with 'y'. Use this when there is no droppable element to name (a canvas, a slider track)."
511
+ },
512
+ "y": {
513
+ "type": "number",
514
+ "description": "Absolute viewport y-coordinate to drop at. Must be given together with 'x'."
515
+ }
516
+ },
517
+ "additionalProperties": false
518
+ },
519
+ "by": {
520
+ "type": "object",
521
+ "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.",
522
+ "properties": {
523
+ "dx": {
524
+ "type": "number",
525
+ "description": "Horizontal offset in CSS pixels; positive drags RIGHT."
526
+ },
527
+ "dy": {
528
+ "type": "number",
529
+ "description": "Vertical offset in CSS pixels; positive drags DOWN."
482
530
  }
483
531
  },
484
532
  "additionalProperties": false
533
+ },
534
+ "mode": {
535
+ "type": "string",
536
+ "enum": [
537
+ "mouse",
538
+ "html5"
539
+ ],
540
+ "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."
541
+ },
542
+ "steps": {
543
+ "type": "number",
544
+ "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."
545
+ }
546
+ },
547
+ "required": [
548
+ "from"
549
+ ],
550
+ "additionalProperties": false
551
+ }
552
+ },
553
+ {
554
+ "name": "scroll",
555
+ "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.",
556
+ "inputSchema": {
557
+ "type": "object",
558
+ "properties": {
559
+ "lease": {
560
+ "type": "string",
561
+ "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."
562
+ },
563
+ "target": {
564
+ "type": "string",
565
+ "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
566
+ },
567
+ "uid": {
568
+ "type": "number",
569
+ "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."
570
+ },
571
+ "selector": {
572
+ "type": "string",
573
+ "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."
574
+ },
575
+ "x": {
576
+ "type": "number",
577
+ "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."
578
+ },
579
+ "y": {
580
+ "type": "number",
581
+ "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."
582
+ },
583
+ "deltaX": {
584
+ "type": "number",
585
+ "description": "Horizontal scroll delta; positive scrolls RIGHT. At least one of deltaX/deltaY is required."
586
+ },
587
+ "deltaY": {
588
+ "type": "number",
589
+ "description": "Vertical scroll delta; positive scrolls DOWN. At least one of deltaX/deltaY is required."
590
+ }
591
+ },
592
+ "required": [],
593
+ "additionalProperties": false
594
+ }
595
+ },
596
+ {
597
+ "name": "dispatch_mouse",
598
+ "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.",
599
+ "inputSchema": {
600
+ "type": "object",
601
+ "properties": {
602
+ "lease": {
603
+ "type": "string",
604
+ "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."
605
+ },
606
+ "target": {
607
+ "type": "string",
608
+ "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
609
+ },
610
+ "action": {
611
+ "type": "string",
612
+ "enum": [
613
+ "move",
614
+ "down",
615
+ "up"
616
+ ],
617
+ "description": "Which raw event to dispatch: 'move' (mouseMoved), 'down' (mousePressed), or 'up' (mouseReleased)."
618
+ },
619
+ "x": {
620
+ "type": "number",
621
+ "description": "Viewport x-coordinate. Required on every call: CDP has no notion of a 'current pointer position' to default from."
622
+ },
623
+ "y": {
624
+ "type": "number",
625
+ "description": "Viewport y-coordinate. Required on every call."
626
+ },
627
+ "button": {
628
+ "type": "string",
629
+ "enum": [
630
+ "left",
631
+ "right",
632
+ "middle"
633
+ ],
634
+ "description": "Mouse button: 'left' (default), 'right', or 'middle'."
635
+ },
636
+ "clickCount": {
637
+ "type": "number",
638
+ "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."
639
+ },
640
+ "modifiers": {
641
+ "type": "array",
642
+ "items": {
643
+ "type": "string",
644
+ "enum": [
645
+ "Alt",
646
+ "Control",
647
+ "Meta",
648
+ "Shift"
649
+ ]
650
+ },
651
+ "description": "Modifier keys held for this one event, e.g. ['Shift']."
485
652
  }
486
653
  },
487
654
  "required": [
488
- "from",
489
- "to"
655
+ "action",
656
+ "x",
657
+ "y"
490
658
  ],
491
659
  "additionalProperties": false
492
660
  }
493
661
  },
662
+ {
663
+ "name": "wait_for_download",
664
+ "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.",
665
+ "inputSchema": {
666
+ "type": "object",
667
+ "properties": {
668
+ "lease": {
669
+ "type": "string",
670
+ "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."
671
+ },
672
+ "target": {
673
+ "type": "string",
674
+ "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<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."
675
+ },
676
+ "arm": {
677
+ "type": "boolean",
678
+ "description": "Arm download capture and return immediately instead of waiting: {armed:true,downloadPath,pending}. Call this BEFORE the click that triggers the download. 'pending' counts downloads that already completed and have not been collected yet, so an arm call doubles as a peek."
679
+ },
680
+ "timeoutMs": {
681
+ "type": "number",
682
+ "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."
683
+ }
684
+ },
685
+ "required": [],
686
+ "additionalProperties": false
687
+ }
688
+ },
689
+ {
690
+ "name": "grant_permissions",
691
+ "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.",
692
+ "inputSchema": {
693
+ "type": "object",
694
+ "properties": {
695
+ "lease": {
696
+ "type": "string",
697
+ "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."
698
+ },
699
+ "target": {
700
+ "type": "string",
701
+ "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'. Used for the lease check and, when 'origin' is omitted, as the source of the origin to grant for."
702
+ },
703
+ "permissions": {
704
+ "type": "array",
705
+ "items": {
706
+ "type": "string"
707
+ },
708
+ "description": "CDP PermissionType values to grant, e.g. ['geolocation'] or ['clipboardReadWrite','clipboardSanitizedWrite']. Required unless reset:true. An empty array is refused rather than treated as a successful no-op."
709
+ },
710
+ "origin": {
711
+ "type": "string",
712
+ "description": "Origin the grant applies to, e.g. 'https://example.com'. Defaults to the target tab's own origin. Required explicitly when that tab has no grantable origin (data:, blob:, about:blank all serialize to the opaque origin 'null'), which is refused with a message naming the tab's url."
713
+ },
714
+ "reset": {
715
+ "type": "boolean",
716
+ "description": "Clear this server's previous permission grants (Browser.resetPermissions). With 'permissions' it resets FIRST and then grants, so the result is exactly the listed permissions; on its own it is a reset-only call answering {reset:true}. Not scoped to 'origin': CDP resets every origin at once."
717
+ }
718
+ },
719
+ "required": [],
720
+ "additionalProperties": false
721
+ }
722
+ },
494
723
  {
495
724
  "name": "fill",
496
725
  "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).",
@@ -499,7 +728,7 @@ export const MANIFEST: ToolSpec[] = [
499
728
  "properties": {
500
729
  "lease": {
501
730
  "type": "string",
502
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
731
+ "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."
503
732
  },
504
733
  "target": {
505
734
  "type": "string",
@@ -532,7 +761,7 @@ export const MANIFEST: ToolSpec[] = [
532
761
  "properties": {
533
762
  "lease": {
534
763
  "type": "string",
535
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
764
+ "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."
536
765
  },
537
766
  "target": {
538
767
  "type": "string",
@@ -578,7 +807,7 @@ export const MANIFEST: ToolSpec[] = [
578
807
  "properties": {
579
808
  "lease": {
580
809
  "type": "string",
581
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
810
+ "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."
582
811
  },
583
812
  "target": {
584
813
  "type": "string",
@@ -611,7 +840,7 @@ export const MANIFEST: ToolSpec[] = [
611
840
  "properties": {
612
841
  "lease": {
613
842
  "type": "string",
614
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
843
+ "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."
615
844
  },
616
845
  "target": {
617
846
  "type": "string",
@@ -643,7 +872,7 @@ export const MANIFEST: ToolSpec[] = [
643
872
  "properties": {
644
873
  "lease": {
645
874
  "type": "string",
646
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
875
+ "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."
647
876
  },
648
877
  "target": {
649
878
  "type": "string",
@@ -682,7 +911,7 @@ export const MANIFEST: ToolSpec[] = [
682
911
  "properties": {
683
912
  "lease": {
684
913
  "type": "string",
685
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
914
+ "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."
686
915
  },
687
916
  "target": {
688
917
  "type": "string",
@@ -733,7 +962,7 @@ export const MANIFEST: ToolSpec[] = [
733
962
  "properties": {
734
963
  "lease": {
735
964
  "type": "string",
736
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
965
+ "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."
737
966
  },
738
967
  "target": {
739
968
  "type": "string",
@@ -833,7 +1062,7 @@ export const MANIFEST: ToolSpec[] = [
833
1062
  "properties": {
834
1063
  "lease": {
835
1064
  "type": "string",
836
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1065
+ "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."
837
1066
  },
838
1067
  "target": {
839
1068
  "type": "string",
@@ -871,7 +1100,7 @@ export const MANIFEST: ToolSpec[] = [
871
1100
  "properties": {
872
1101
  "lease": {
873
1102
  "type": "string",
874
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1103
+ "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."
875
1104
  },
876
1105
  "target": {
877
1106
  "type": "string",
@@ -908,7 +1137,7 @@ export const MANIFEST: ToolSpec[] = [
908
1137
  "properties": {
909
1138
  "lease": {
910
1139
  "type": "string",
911
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1140
+ "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."
912
1141
  },
913
1142
  "target": {
914
1143
  "type": "string",
@@ -935,7 +1164,7 @@ export const MANIFEST: ToolSpec[] = [
935
1164
  "properties": {
936
1165
  "lease": {
937
1166
  "type": "string",
938
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1167
+ "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."
939
1168
  },
940
1169
  "target": {
941
1170
  "type": "string",
@@ -958,7 +1187,7 @@ export const MANIFEST: ToolSpec[] = [
958
1187
  "properties": {
959
1188
  "lease": {
960
1189
  "type": "string",
961
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1190
+ "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."
962
1191
  },
963
1192
  "target": {
964
1193
  "type": "string",
@@ -989,7 +1218,7 @@ export const MANIFEST: ToolSpec[] = [
989
1218
  "properties": {
990
1219
  "lease": {
991
1220
  "type": "string",
992
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1221
+ "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."
993
1222
  },
994
1223
  "target": {
995
1224
  "type": "string",
@@ -1024,7 +1253,7 @@ export const MANIFEST: ToolSpec[] = [
1024
1253
  "properties": {
1025
1254
  "lease": {
1026
1255
  "type": "string",
1027
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1256
+ "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."
1028
1257
  },
1029
1258
  "target": {
1030
1259
  "type": "string",
@@ -1050,7 +1279,7 @@ export const MANIFEST: ToolSpec[] = [
1050
1279
  "properties": {
1051
1280
  "lease": {
1052
1281
  "type": "string",
1053
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1282
+ "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."
1054
1283
  },
1055
1284
  "target": {
1056
1285
  "type": "string",
@@ -1069,7 +1298,7 @@ export const MANIFEST: ToolSpec[] = [
1069
1298
  "properties": {
1070
1299
  "lease": {
1071
1300
  "type": "string",
1072
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1301
+ "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."
1073
1302
  },
1074
1303
  "tracePath": {
1075
1304
  "type": "string",
@@ -1088,7 +1317,7 @@ export const MANIFEST: ToolSpec[] = [
1088
1317
  "properties": {
1089
1318
  "lease": {
1090
1319
  "type": "string",
1091
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1320
+ "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."
1092
1321
  },
1093
1322
  "target": {
1094
1323
  "type": "string",
@@ -1126,7 +1355,7 @@ export const MANIFEST: ToolSpec[] = [
1126
1355
  "properties": {
1127
1356
  "lease": {
1128
1357
  "type": "string",
1129
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1358
+ "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."
1130
1359
  },
1131
1360
  "target": {
1132
1361
  "type": "string",
@@ -1173,7 +1402,7 @@ export const MANIFEST: ToolSpec[] = [
1173
1402
  "properties": {
1174
1403
  "lease": {
1175
1404
  "type": "string",
1176
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1405
+ "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."
1177
1406
  },
1178
1407
  "target": {
1179
1408
  "type": "string",
@@ -1196,7 +1425,7 @@ export const MANIFEST: ToolSpec[] = [
1196
1425
  "properties": {
1197
1426
  "lease": {
1198
1427
  "type": "string",
1199
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1428
+ "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."
1200
1429
  },
1201
1430
  "target": {
1202
1431
  "type": "string",
@@ -1219,7 +1448,7 @@ export const MANIFEST: ToolSpec[] = [
1219
1448
  "properties": {
1220
1449
  "lease": {
1221
1450
  "type": "string",
1222
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1451
+ "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."
1223
1452
  },
1224
1453
  "url": {
1225
1454
  "type": "string",
@@ -1263,7 +1492,7 @@ export const MANIFEST: ToolSpec[] = [
1263
1492
  "properties": {
1264
1493
  "lease": {
1265
1494
  "type": "string",
1266
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1495
+ "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."
1267
1496
  },
1268
1497
  "target": {
1269
1498
  "type": "string",
@@ -1333,7 +1562,7 @@ export const MANIFEST: ToolSpec[] = [
1333
1562
  "properties": {
1334
1563
  "lease": {
1335
1564
  "type": "string",
1336
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1565
+ "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."
1337
1566
  },
1338
1567
  "target": {
1339
1568
  "type": "string",
@@ -1352,7 +1581,7 @@ export const MANIFEST: ToolSpec[] = [
1352
1581
  "properties": {
1353
1582
  "lease": {
1354
1583
  "type": "string",
1355
- "description": "Opaque lease token from claim_page. Required only when the tab this call resolves to is leased by someone; omit it for unleased tabs, which behave exactly as before."
1584
+ "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."
1356
1585
  },
1357
1586
  "target": {
1358
1587
  "type": "string",
@@ -1369,17 +1598,21 @@ export const MANIFEST: ToolSpec[] = [
1369
1598
  },
1370
1599
  {
1371
1600
  "name": "claim_page",
1372
- "description": "Take exclusive ownership of a browser tab and return an opaque lease token. With no targetId, opens a fresh tab (optionally at url) and claims it, so 'give me my own tab' is one call. 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.",
1601
+ "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.",
1373
1602
  "inputSchema": {
1374
1603
  "type": "object",
1375
1604
  "properties": {
1605
+ "target": {
1606
+ "type": "string",
1607
+ "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> | <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."
1608
+ },
1376
1609
  "targetId": {
1377
1610
  "type": "string",
1378
- "description": "Claim this already-open page target instead of opening a new tab. Must be an exact target id, not a selector."
1611
+ "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."
1379
1612
  },
1380
1613
  "url": {
1381
1614
  "type": "string",
1382
- "description": "When opening a fresh tab, navigate it here. Ignored when targetId is given."
1615
+ "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."
1383
1616
  },
1384
1617
  "label": {
1385
1618
  "type": "string",
@@ -1396,24 +1629,30 @@ export const MANIFEST: ToolSpec[] = [
1396
1629
  },
1397
1630
  {
1398
1631
  "name": "release_page",
1399
- "description": "Release a held lease, given its token. Idempotent: releasing an already-released, reclaimed, or expired lease reports released:false rather than erroring. Does not close the tab.",
1632
+ "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}.",
1400
1633
  "inputSchema": {
1401
1634
  "type": "object",
1402
1635
  "properties": {
1403
1636
  "lease": {
1404
1637
  "type": "string",
1405
- "description": "The opaque token claim_page (or new_page with claim:true) returned."
1638
+ "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."
1639
+ },
1640
+ "target": {
1641
+ "type": "string",
1642
+ "description": "Target selector for a tab this process holds: active | index:N | url:<substr> | title:<substr> | <targetId>. Mutually exclusive with 'lease'. Refused if another process holds the tab."
1643
+ },
1644
+ "close": {
1645
+ "type": "boolean",
1646
+ "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."
1406
1647
  }
1407
1648
  },
1408
- "required": [
1409
- "lease"
1410
- ],
1649
+ "required": [],
1411
1650
  "additionalProperties": false
1412
1651
  }
1413
1652
  },
1414
1653
  {
1415
1654
  "name": "list_leases",
1416
- "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.",
1655
+ "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.",
1417
1656
  "inputSchema": {
1418
1657
  "type": "object",
1419
1658
  "properties": {},