cdp-toolkit 1.0.0 → 1.2.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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * MCP tool manifest — the JSON Schemas the cdp-toolkit MCP server advertises via
2
+ * MCP tool manifest: the JSON Schemas the cdp-toolkit MCP server advertises via
3
3
  * `tools/list`. Each entry's `name` matches a key in the TOOLS registry
4
4
  * (src/index.ts) and its `inputSchema` mirrors that tool's TypeScript Args.
5
5
  *
@@ -29,6 +29,10 @@ export const MANIFEST: ToolSpec[] = [
29
29
  "inputSchema": {
30
30
  "type": "object",
31
31
  "properties": {
32
+ "lease": {
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."
35
+ },
32
36
  "all": {
33
37
  "type": "boolean",
34
38
  "description": "Include non-page targets (service/shared workers, background pages) when true; otherwise only page-type tabs are listed."
@@ -44,9 +48,25 @@ export const MANIFEST: ToolSpec[] = [
44
48
  "inputSchema": {
45
49
  "type": "object",
46
50
  "properties": {
51
+ "lease": {
52
+ "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."
54
+ },
47
55
  "url": {
48
56
  "type": "string",
49
57
  "description": "URL to open in the new tab; defaults to about:blank."
58
+ },
59
+ "claim": {
60
+ "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."
62
+ },
63
+ "label": {
64
+ "type": "string",
65
+ "description": "Agent label recorded on the lease when claim:true, surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
66
+ },
67
+ "ttlMs": {
68
+ "type": "number",
69
+ "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."
50
70
  }
51
71
  },
52
72
  "required": [],
@@ -59,6 +79,10 @@ export const MANIFEST: ToolSpec[] = [
59
79
  "inputSchema": {
60
80
  "type": "object",
61
81
  "properties": {
82
+ "lease": {
83
+ "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."
85
+ },
62
86
  "target": {
63
87
  "type": "string",
64
88
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -76,6 +100,10 @@ export const MANIFEST: ToolSpec[] = [
76
100
  "inputSchema": {
77
101
  "type": "object",
78
102
  "properties": {
103
+ "lease": {
104
+ "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."
106
+ },
79
107
  "target": {
80
108
  "type": "string",
81
109
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -93,6 +121,10 @@ export const MANIFEST: ToolSpec[] = [
93
121
  "inputSchema": {
94
122
  "type": "object",
95
123
  "properties": {
124
+ "lease": {
125
+ "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."
127
+ },
96
128
  "target": {
97
129
  "type": "string",
98
130
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -127,10 +159,14 @@ export const MANIFEST: ToolSpec[] = [
127
159
  },
128
160
  {
129
161
  "name": "wait_for",
130
- "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}.",
162
+ "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}.",
131
163
  "inputSchema": {
132
164
  "type": "object",
133
165
  "properties": {
166
+ "lease": {
167
+ "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."
169
+ },
134
170
  "target": {
135
171
  "type": "string",
136
172
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -160,6 +196,10 @@ export const MANIFEST: ToolSpec[] = [
160
196
  "inputSchema": {
161
197
  "type": "object",
162
198
  "properties": {
199
+ "lease": {
200
+ "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."
202
+ },
163
203
  "target": {
164
204
  "type": "string",
165
205
  "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."
@@ -185,10 +225,14 @@ export const MANIFEST: ToolSpec[] = [
185
225
  },
186
226
  {
187
227
  "name": "take_snapshot",
188
- "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.",
228
+ "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.",
189
229
  "inputSchema": {
190
230
  "type": "object",
191
231
  "properties": {
232
+ "lease": {
233
+ "type": "string",
234
+ "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."
235
+ },
192
236
  "target": {
193
237
  "type": "string",
194
238
  "description": "Target page selector: 'active' (default, = first page-type target) | 'index:N' (Nth page-type target, 0-based) | 'url:<substring>' | 'title:<substring>' | '<32-hex targetId>' (exact target by id)."
@@ -208,6 +252,10 @@ export const MANIFEST: ToolSpec[] = [
208
252
  "inputSchema": {
209
253
  "type": "object",
210
254
  "properties": {
255
+ "lease": {
256
+ "type": "string",
257
+ "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."
258
+ },
211
259
  "target": {
212
260
  "type": "string",
213
261
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -244,6 +292,10 @@ export const MANIFEST: ToolSpec[] = [
244
292
  "inputSchema": {
245
293
  "type": "object",
246
294
  "properties": {
295
+ "lease": {
296
+ "type": "string",
297
+ "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."
298
+ },
247
299
  "target": {
248
300
  "type": "string",
249
301
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -267,6 +319,10 @@ export const MANIFEST: ToolSpec[] = [
267
319
  "inputSchema": {
268
320
  "type": "object",
269
321
  "properties": {
322
+ "lease": {
323
+ "type": "string",
324
+ "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."
325
+ },
270
326
  "target": {
271
327
  "type": "string",
272
328
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -315,6 +371,10 @@ export const MANIFEST: ToolSpec[] = [
315
371
  "inputSchema": {
316
372
  "type": "object",
317
373
  "properties": {
374
+ "lease": {
375
+ "type": "string",
376
+ "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."
377
+ },
318
378
  "target": {
319
379
  "type": "string",
320
380
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -344,6 +404,10 @@ export const MANIFEST: ToolSpec[] = [
344
404
  "inputSchema": {
345
405
  "type": "object",
346
406
  "properties": {
407
+ "lease": {
408
+ "type": "string",
409
+ "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."
410
+ },
347
411
  "target": {
348
412
  "type": "string",
349
413
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -386,6 +450,10 @@ export const MANIFEST: ToolSpec[] = [
386
450
  "inputSchema": {
387
451
  "type": "object",
388
452
  "properties": {
453
+ "lease": {
454
+ "type": "string",
455
+ "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."
456
+ },
389
457
  "target": {
390
458
  "type": "string",
391
459
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -415,6 +483,10 @@ export const MANIFEST: ToolSpec[] = [
415
483
  "inputSchema": {
416
484
  "type": "object",
417
485
  "properties": {
486
+ "lease": {
487
+ "type": "string",
488
+ "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."
489
+ },
418
490
  "target": {
419
491
  "type": "string",
420
492
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -443,6 +515,10 @@ export const MANIFEST: ToolSpec[] = [
443
515
  "inputSchema": {
444
516
  "type": "object",
445
517
  "properties": {
518
+ "lease": {
519
+ "type": "string",
520
+ "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."
521
+ },
446
522
  "target": {
447
523
  "type": "string",
448
524
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -474,10 +550,14 @@ export const MANIFEST: ToolSpec[] = [
474
550
  },
475
551
  {
476
552
  "name": "take_screenshot",
477
- "description": "Capture the viewport (default), the full scrollable page (fullPage), or a single element (uid or selector — exactly one, mutually exclusive) via raw CDP Page.captureScreenshot. Writes a PNG/JPEG under /tmp/cdp-toolkit (override with savePath) and returns {path,bytes,format,target}; raw base64 is only included when returnBase64 is set. quality applies to jpeg only.",
553
+ "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. Writes a PNG/JPEG under /tmp/cdp-toolkit (override with savePath) and returns {path,bytes,format,target}; raw base64 is only included when returnBase64 is set. quality applies to jpeg only.",
478
554
  "inputSchema": {
479
555
  "type": "object",
480
556
  "properties": {
557
+ "lease": {
558
+ "type": "string",
559
+ "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."
560
+ },
481
561
  "target": {
482
562
  "type": "string",
483
563
  "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."
@@ -500,7 +580,7 @@ export const MANIFEST: ToolSpec[] = [
500
580
  },
501
581
  "uid": {
502
582
  "type": "number",
503
- "description": "Element to clip to — a CDP backendDOMNodeId obtained from take_snapshot. Mutually exclusive with selector."
583
+ "description": "Element to clip to: a CDP backendDOMNodeId obtained from take_snapshot. Mutually exclusive with selector."
504
584
  },
505
585
  "selector": {
506
586
  "type": "string",
@@ -525,6 +605,10 @@ export const MANIFEST: ToolSpec[] = [
525
605
  "inputSchema": {
526
606
  "type": "object",
527
607
  "properties": {
608
+ "lease": {
609
+ "type": "string",
610
+ "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."
611
+ },
528
612
  "target": {
529
613
  "type": "string",
530
614
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -621,6 +705,10 @@ export const MANIFEST: ToolSpec[] = [
621
705
  "inputSchema": {
622
706
  "type": "object",
623
707
  "properties": {
708
+ "lease": {
709
+ "type": "string",
710
+ "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."
711
+ },
624
712
  "target": {
625
713
  "type": "string",
626
714
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -655,6 +743,10 @@ export const MANIFEST: ToolSpec[] = [
655
743
  "inputSchema": {
656
744
  "type": "object",
657
745
  "properties": {
746
+ "lease": {
747
+ "type": "string",
748
+ "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."
749
+ },
658
750
  "target": {
659
751
  "type": "string",
660
752
  "description": "Target page selector: 'active' (or omitted) for the first page-type target | 'index:N' (0-based) | 'url:<substring>' | 'title:<substring>' | a 32-hex <targetId>."
@@ -688,6 +780,10 @@ export const MANIFEST: ToolSpec[] = [
688
780
  "inputSchema": {
689
781
  "type": "object",
690
782
  "properties": {
783
+ "lease": {
784
+ "type": "string",
785
+ "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."
786
+ },
691
787
  "target": {
692
788
  "type": "string",
693
789
  "description": "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
@@ -707,10 +803,14 @@ export const MANIFEST: ToolSpec[] = [
707
803
  },
708
804
  {
709
805
  "name": "get_console_message",
710
- "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.",
806
+ "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.",
711
807
  "inputSchema": {
712
808
  "type": "object",
713
809
  "properties": {
810
+ "lease": {
811
+ "type": "string",
812
+ "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."
813
+ },
714
814
  "target": {
715
815
  "type": "string",
716
816
  "description": "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
@@ -730,6 +830,10 @@ export const MANIFEST: ToolSpec[] = [
730
830
  "inputSchema": {
731
831
  "type": "object",
732
832
  "properties": {
833
+ "lease": {
834
+ "type": "string",
835
+ "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."
836
+ },
733
837
  "target": {
734
838
  "type": "string",
735
839
  "description": "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
@@ -753,17 +857,21 @@ export const MANIFEST: ToolSpec[] = [
753
857
  },
754
858
  {
755
859
  "name": "get_network_request",
756
- "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 reload capture and is matched by url ONLY (reload re-mints requestIds, so a carried-over requestId cannot fetch a body — it returns metadata plus bodyUnavailableReason).",
860
+ "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 reload capture and is matched by url ONLY (reload re-mints requestIds, so a carried-over requestId cannot fetch a body, so it returns metadata plus bodyUnavailableReason).",
757
861
  "inputSchema": {
758
862
  "type": "object",
759
863
  "properties": {
864
+ "lease": {
865
+ "type": "string",
866
+ "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."
867
+ },
760
868
  "target": {
761
869
  "type": "string",
762
870
  "description": "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
763
871
  },
764
872
  "requestId": {
765
873
  "type": "string",
766
- "description": "Match by exact requestId (metadata only — cannot fetch a body, since reload re-mints requestIds)."
874
+ "description": "Match by exact requestId (metadata only, cannot fetch a body, since reload re-mints requestIds)."
767
875
  },
768
876
  "url": {
769
877
  "type": "string",
@@ -788,6 +896,10 @@ export const MANIFEST: ToolSpec[] = [
788
896
  "inputSchema": {
789
897
  "type": "object",
790
898
  "properties": {
899
+ "lease": {
900
+ "type": "string",
901
+ "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."
902
+ },
791
903
  "target": {
792
904
  "type": "string",
793
905
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -806,10 +918,14 @@ export const MANIFEST: ToolSpec[] = [
806
918
  },
807
919
  {
808
920
  "name": "performance_stop_trace",
809
- "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.",
921
+ "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.",
810
922
  "inputSchema": {
811
923
  "type": "object",
812
924
  "properties": {
925
+ "lease": {
926
+ "type": "string",
927
+ "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."
928
+ },
813
929
  "target": {
814
930
  "type": "string",
815
931
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -821,10 +937,14 @@ export const MANIFEST: ToolSpec[] = [
821
937
  },
822
938
  {
823
939
  "name": "performance_analyze_insight",
824
- "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).",
940
+ "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).",
825
941
  "inputSchema": {
826
942
  "type": "object",
827
943
  "properties": {
944
+ "lease": {
945
+ "type": "string",
946
+ "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."
947
+ },
828
948
  "tracePath": {
829
949
  "type": "string",
830
950
  "description": "Filesystem path to a trace JSON file written by performance_trace or performance_stop_trace (bare array OR {traceEvents:[...]}). Required."
@@ -836,10 +956,14 @@ export const MANIFEST: ToolSpec[] = [
836
956
  },
837
957
  {
838
958
  "name": "performance_trace",
839
- "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 — the recommended entry point.",
959
+ "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.",
840
960
  "inputSchema": {
841
961
  "type": "object",
842
962
  "properties": {
963
+ "lease": {
964
+ "type": "string",
965
+ "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."
966
+ },
843
967
  "target": {
844
968
  "type": "string",
845
969
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -874,6 +998,10 @@ export const MANIFEST: ToolSpec[] = [
874
998
  "inputSchema": {
875
999
  "type": "object",
876
1000
  "properties": {
1001
+ "lease": {
1002
+ "type": "string",
1003
+ "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."
1004
+ },
877
1005
  "target": {
878
1006
  "type": "string",
879
1007
  "description": "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
@@ -893,9 +1021,13 @@ export const MANIFEST: ToolSpec[] = [
893
1021
  "inputSchema": {
894
1022
  "type": "object",
895
1023
  "properties": {
1024
+ "lease": {
1025
+ "type": "string",
1026
+ "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."
1027
+ },
896
1028
  "url": {
897
1029
  "type": "string",
898
- "description": "The URL to audit. Required — never points at a user tab implicitly."
1030
+ "description": "The URL to audit. Required, and never points at a user tab implicitly."
899
1031
  },
900
1032
  "categories": {
901
1033
  "type": "array",
@@ -929,10 +1061,14 @@ export const MANIFEST: ToolSpec[] = [
929
1061
  },
930
1062
  {
931
1063
  "name": "mock_request",
932
- "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).",
1064
+ "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).",
933
1065
  "inputSchema": {
934
1066
  "type": "object",
935
1067
  "properties": {
1068
+ "lease": {
1069
+ "type": "string",
1070
+ "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."
1071
+ },
936
1072
  "target": {
937
1073
  "type": "string",
938
1074
  "description": "Target selector: active | index:N | url:<substr> | title:<substr> | <targetId>. Defaults to the active page."
@@ -999,6 +1135,10 @@ export const MANIFEST: ToolSpec[] = [
999
1135
  "inputSchema": {
1000
1136
  "type": "object",
1001
1137
  "properties": {
1138
+ "lease": {
1139
+ "type": "string",
1140
+ "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."
1141
+ },
1002
1142
  "target": {
1003
1143
  "type": "string",
1004
1144
  "description": "Unused filter placeholder; list_mocks returns all active sessions."
@@ -1014,6 +1154,10 @@ export const MANIFEST: ToolSpec[] = [
1014
1154
  "inputSchema": {
1015
1155
  "type": "object",
1016
1156
  "properties": {
1157
+ "lease": {
1158
+ "type": "string",
1159
+ "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."
1160
+ },
1017
1161
  "target": {
1018
1162
  "type": "string",
1019
1163
  "description": "Target selector whose mock session to clear. Defaults to the active page. Ignored when all:true."
@@ -1026,5 +1170,59 @@ export const MANIFEST: ToolSpec[] = [
1026
1170
  "required": [],
1027
1171
  "additionalProperties": false
1028
1172
  }
1173
+ },
1174
+ {
1175
+ "name": "claim_page",
1176
+ "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.",
1177
+ "inputSchema": {
1178
+ "type": "object",
1179
+ "properties": {
1180
+ "targetId": {
1181
+ "type": "string",
1182
+ "description": "Claim this already-open page target instead of opening a new tab. Must be an exact target id, not a selector."
1183
+ },
1184
+ "url": {
1185
+ "type": "string",
1186
+ "description": "When opening a fresh tab, navigate it here. Ignored when targetId is given."
1187
+ },
1188
+ "label": {
1189
+ "type": "string",
1190
+ "description": "Agent label recorded on the lease and surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
1191
+ },
1192
+ "ttlMs": {
1193
+ "type": "number",
1194
+ "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."
1195
+ }
1196
+ },
1197
+ "required": [],
1198
+ "additionalProperties": false
1199
+ }
1200
+ },
1201
+ {
1202
+ "name": "release_page",
1203
+ "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.",
1204
+ "inputSchema": {
1205
+ "type": "object",
1206
+ "properties": {
1207
+ "lease": {
1208
+ "type": "string",
1209
+ "description": "The opaque token claim_page (or new_page with claim:true) returned."
1210
+ }
1211
+ },
1212
+ "required": [
1213
+ "lease"
1214
+ ],
1215
+ "additionalProperties": false
1216
+ }
1217
+ },
1218
+ {
1219
+ "name": "list_leases",
1220
+ "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.",
1221
+ "inputSchema": {
1222
+ "type": "object",
1223
+ "properties": {},
1224
+ "required": [],
1225
+ "additionalProperties": false
1226
+ }
1029
1227
  }
1030
1228
  ];
package/src/mcp.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * cdp-toolkit MCP server (stdio).
4
4
  *
5
5
  * Exposes the toolkit's raw-CDP tools to any MCP client (Claude Code, etc.) over
6
- * the standard stdio transport. It does NOT connect to Chrome at startup — each
6
+ * the standard stdio transport. It does NOT connect to Chrome at startup: each
7
7
  * tool call lazily opens a single-target CDP connection (with its own timeout),
8
8
  * so the server loads cleanly even when Chrome isn't running; individual calls
9
9
  * then fail with a clear error if the browser is unreachable.
@@ -11,18 +11,29 @@
11
11
  * Launch: `bunx -y cdp-toolkit` (or `bun run src/mcp.ts` from a checkout)
12
12
  * Config: CDP_BASE (default http://127.0.0.1:9222), CDP_TIMEOUT_MS, CDP_ARTIFACT_DIR.
13
13
  *
14
- * stdout is the JSON-RPC channel — all diagnostics go to stderr only.
14
+ * stdout is the JSON-RPC channel, all diagnostics go to stderr only.
15
15
  */
16
16
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17
17
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
18
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
19
19
  import { TOOLS, TOOL_NAMES, BASE } from "./index.ts";
20
20
  import { MANIFEST } from "./manifest.ts";
21
+ import { resolveBrowserKind, stripBrowserFlag, getOrCreateFirefoxSession, disposeFirefoxSession } from "./backend.ts";
22
+ import { toolAvailability } from "./capabilities.ts";
23
+ import { FIREFOX_TOOLS } from "./firefox-tools.ts";
24
+ import { leaseFromArgs, withLeaseScope } from "./leases.ts";
21
25
 
22
- const VERSION = "1.0.0";
26
+ const VERSION = "1.2.0";
27
+
28
+ // --browser is read once at startup (MCP has no per-call notion of backend): explicit flag,
29
+ // else CDP_BROWSER env var, else "chrome" (zero behavior change for existing users/configs).
30
+ const BROWSER = resolveBrowserKind(stripBrowserFlag(process.argv.slice(2)));
31
+ const AVAILABILITY = toolAvailability(BROWSER);
32
+ const AVAILABLE_NAMES = new Set<string>(AVAILABILITY.available);
23
33
 
24
34
  /** Loose dispatch view of the strongly-typed TOOLS registry. */
25
35
  const dispatch = TOOLS as Record<string, (args: unknown) => Promise<unknown>>;
36
+ const neutralDispatch = FIREFOX_TOOLS as Record<string, (driver: import("./driver.ts").BrowserDriver, args: unknown) => Promise<unknown>>;
26
37
 
27
38
  /** Warn (to stderr) about any registry/manifest drift, but don't fail startup. */
28
39
  function auditCoverage(): void {
@@ -37,25 +48,72 @@ function auditCoverage(): void {
37
48
  const server = new Server({ name: "cdp-toolkit", version: VERSION }, { capabilities: { tools: {} } });
38
49
 
39
50
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
40
- tools: MANIFEST.map((s) => ({ name: s.name, description: s.description, inputSchema: s.inputSchema })),
51
+ // Filtered to whatever the selected backend can actually run, per REQUIRED_CAPABILITIES:
52
+ // under Chrome this is byte-identical to the unfiltered MANIFEST (every capability CDP needs
53
+ // is declared); under Firefox the performance/heap/lighthouse tools are simply absent.
54
+ tools: MANIFEST.filter((s) => AVAILABLE_NAMES.has(s.name)).map((s) => ({ name: s.name, description: s.description, inputSchema: s.inputSchema })),
41
55
  }));
42
56
 
43
57
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
44
58
  const { name } = request.params;
45
59
  const args = (request.params.arguments ?? {}) as unknown;
46
- const fn = dispatch[name];
47
- if (!fn) {
48
- return { content: [{ type: "text" as const, text: `unknown tool: ${name}` }], isError: true };
49
- }
50
- try {
51
- const result = await fn(args);
52
- return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
53
- } catch (err) {
54
- const message = err instanceof Error ? err.message : String(err);
55
- return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true };
60
+
61
+ if (!AVAILABLE_NAMES.has(name)) {
62
+ const gap = AVAILABILITY.unavailable.find((u) => u.name === name);
63
+ return {
64
+ content: [{ type: "text" as const, text: `unknown tool: ${name}${gap ? ` (not available under --browser ${BROWSER}, needs: ${gap.missing.join(", ")})` : ""}` }],
65
+ isError: true,
66
+ };
56
67
  }
68
+
69
+ // ONE lease scope per dispatch, wrapping BOTH backend branches. This is the
70
+ // reason no tool takes a lease parameter: the token rides the async context
71
+ // down to whichever resolution path the tool eventually reaches, so a tool
72
+ // added tomorrow is covered with no action from whoever writes it. Reading
73
+ // 'lease' off args here is the only place the MCP layer knows the key exists.
74
+ return withLeaseScope(leaseFromArgs(args), async () => {
75
+ if (BROWSER === "chrome") {
76
+ const fn = dispatch[name];
77
+ if (!fn) return { content: [{ type: "text" as const, text: `unknown tool: ${name}` }], isError: true };
78
+ try {
79
+ const result = await fn(args);
80
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
81
+ } catch (err) {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true };
84
+ }
85
+ }
86
+
87
+ // Firefox: one BiDi session memoized for the life of this server process (lifetime "session",
88
+ // ADR-001); launched lazily on the first Firefox tool call, torn down on shutdown below.
89
+ const neutralFn = neutralDispatch[name];
90
+ if (!neutralFn) return { content: [{ type: "text" as const, text: `unknown tool: ${name}` }], isError: true };
91
+ try {
92
+ const session = await getOrCreateFirefoxSession();
93
+ const result = await neutralFn(session.driver, args);
94
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
95
+ } catch (err) {
96
+ const message = err instanceof Error ? err.message : String(err);
97
+ return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true };
98
+ }
99
+ });
57
100
  });
58
101
 
59
102
  auditCoverage();
60
103
  await server.connect(new StdioServerTransport());
61
- console.error(`[cdp-toolkit] MCP server v${VERSION} ready — ${MANIFEST.length} tools, CDP_BASE=${BASE}`);
104
+ console.error(`[cdp-toolkit] MCP server v${VERSION} ready, browser=${BROWSER}, ${AVAILABILITY.available.length} tools, CDP_BASE=${BASE}`);
105
+
106
+ // Firefox owns a real OS process (see bidi/launch.ts): it must be reaped on every shutdown path,
107
+ // not just a clean exit. SIGINT/SIGTERM cover ctrl-C and a supervising client killing the server;
108
+ // stdin 'close' covers the normal MCP shutdown (the client closes the stdio pipe). All three are
109
+ // idempotent through disposeFirefoxSession(), and a no-op entirely when Firefox was never launched.
110
+ let shuttingDown = false;
111
+ async function shutdown(): Promise<void> {
112
+ if (shuttingDown) return;
113
+ shuttingDown = true;
114
+ await disposeFirefoxSession();
115
+ process.exit(0);
116
+ }
117
+ process.on("SIGINT", () => void shutdown());
118
+ process.on("SIGTERM", () => void shutdown());
119
+ process.stdin.on("close", () => void shutdown());