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/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * cdp-toolkit public surface.
3
3
  *
4
4
  * Two exports:
5
- * 1. `TOOLS` — the registry mapping every chrome-devtools-mcp tool name
5
+ * 1. `TOOLS` : the registry mapping every chrome-devtools-mcp tool name
6
6
  * (snake_case string, all 29) to its raw-CDP implementation function. The
7
7
  * CLI and any embedder dispatch through this single table.
8
8
  * 2. The client primitives, re-exported so consumers can build their own
@@ -12,21 +12,15 @@
12
12
  * value (or throws). See CONTRACT.md for the design rules each module follows.
13
13
  */
14
14
 
15
- // --- pages (browser endpoint: Target.*) ---
16
- import { listPages, newPage, closePage, selectPage } from "./tools/pages.ts";
17
- // --- navigation ---
18
- import { navigatePage, waitForText } from "./tools/navigation.ts";
19
- // --- evaluate ---
20
- import { evaluateScript } from "./tools/evaluate.ts";
21
- // --- snapshot (a11y tree + shared resolveUid helper) ---
22
- import { takeSnapshot } from "./tools/snapshot.ts";
23
- // --- input / interaction ---
24
- import { click, hover, drag, fill, fillForm, typeText, pressKey, uploadFile } from "./tools/input.ts";
25
- // --- screenshot + emulation ---
26
- import { takeScreenshot } from "./tools/screenshot.ts";
27
- import { emulate, resizePage } from "./tools/emulation.ts";
28
- // --- dialogs ---
29
- import { handleDialog } from "./tools/dialogs.ts";
15
+ // --- the 20 tools unified with Firefox onto one Driver-based implementation ---
16
+ // (list_pages/new_page/close_page/select_page, navigate_page/wait_for, evaluate_script,
17
+ // take_snapshot, click/hover/drag/fill/fill_form/type_text/press_key/upload_file,
18
+ // take_screenshot/emulate/resize_page, handle_dialog). See shared-tools.ts's file header for
19
+ // the byte-identical-Chrome-behavior contract this bind honors, and for the 7 tools that stay
20
+ // two implementations (console/network/mock, immediately below, unchanged from before).
21
+ import { createCdpDriver } from "./cdp/driver.ts";
22
+ import { LEASE_TOOLS } from "./leases-tools.ts";
23
+ import { SHARED_TOOLS } from "./shared-tools.ts";
30
24
  // --- console + network (read the recorder buffer) ---
31
25
  import { listConsoleMessages, getConsoleMessage } from "./tools/console.ts";
32
26
  import { listNetworkRequests, getNetworkRequest } from "./tools/network.ts";
@@ -41,49 +35,64 @@ import {
41
35
  import { takeHeapsnapshot } from "./tools/heap.ts";
42
36
  // --- lighthouse (the sole non-CDP tool) ---
43
37
  import { lighthouseAudit } from "./tools/lighthouse.ts";
44
- // --- network mocking (Fetch domain) — toolkit addition beyond the 29 parity tools ---
38
+ // --- network mocking (Fetch domain) : toolkit addition beyond the 29 parity tools ---
45
39
  import { mockRequest, listMocks, clearMocks } from "./tools/network_mock.ts";
46
40
 
47
41
  /** A toolkit tool: a single typed-args function returning JSON-serializable data. */
48
42
  export type ToolFn = (args: never) => Promise<unknown>;
49
43
 
44
+ // One stateless CDP driver, reused across calls (lifetime "per-call": every page() acquisition
45
+ // still opens its own throwaway connection, same as the pre-migration withPage per-call model).
46
+ // Constructing it does not dial a browser (capabilities.ts already relies on this).
47
+ const cdpDriver = createCdpDriver();
48
+
49
+ /** Bind a shared-tools.ts function to the module-level CDP driver, args-typed for the registry. */
50
+ function onCdp<A>(fn: (driver: ReturnType<typeof createCdpDriver>, args: A) => Promise<unknown>): (args: A) => Promise<unknown> {
51
+ return (args: A) => fn(cdpDriver, args);
52
+ }
53
+
50
54
  /**
51
55
  * The complete chrome-devtools-mcp parity surface: all 29 MCP tools keyed by
52
56
  * their canonical MCP (snake_case) name, plus one convenience superset tool
53
- * (`performance_trace`, a robust single-call trace — the start/stop pair cannot
57
+ * (`performance_trace`, a robust single-call trace : the start/stop pair cannot
54
58
  * span two stateless CLI processes), plus a 3-tool network-mocking group
55
- * (mock_request/list_mocks/clear_mocks — a persistent per-target fake backend).
56
- * 33 entries total. Listed explicitly so the mapping is auditable at a glance and
59
+ * (mock_request/list_mocks/clear_mocks : a persistent per-target fake backend).
60
+ * 36 entries total. Listed explicitly so the mapping is auditable at a glance and
57
61
  * the CLI can `--list` it.
62
+ *
63
+ * 20 of the 29 MCP-parity tools (pages/navigation/evaluate/snapshot/interaction/
64
+ * screenshot+emulation/dialogs) now route through shared-tools.ts's single Driver-based
65
+ * implementation via the CDP driver, instead of a Chrome-only copy in src/tools/*.ts.
66
+ * See shared-tools.ts's file header for the shape/behavior contract this preserves.
58
67
  */
59
68
  export const TOOLS = {
60
69
  // pages (4)
61
- list_pages: listPages,
62
- new_page: newPage,
63
- close_page: closePage,
64
- select_page: selectPage,
70
+ list_pages: onCdp(SHARED_TOOLS.list_pages),
71
+ new_page: onCdp(SHARED_TOOLS.new_page),
72
+ close_page: onCdp(SHARED_TOOLS.close_page),
73
+ select_page: onCdp(SHARED_TOOLS.select_page),
65
74
  // navigation (2)
66
- navigate_page: navigatePage,
67
- wait_for: waitForText,
75
+ navigate_page: onCdp(SHARED_TOOLS.navigate_page),
76
+ wait_for: onCdp(SHARED_TOOLS.wait_for),
68
77
  // evaluate (1)
69
- evaluate_script: evaluateScript,
78
+ evaluate_script: onCdp(SHARED_TOOLS.evaluate_script),
70
79
  // snapshot (1)
71
- take_snapshot: takeSnapshot,
80
+ take_snapshot: onCdp(SHARED_TOOLS.take_snapshot),
72
81
  // interaction (8)
73
- click: click,
74
- hover: hover,
75
- drag: drag,
76
- fill: fill,
77
- fill_form: fillForm,
78
- type_text: typeText,
79
- press_key: pressKey,
80
- upload_file: uploadFile,
82
+ click: onCdp(SHARED_TOOLS.click),
83
+ hover: onCdp(SHARED_TOOLS.hover),
84
+ drag: onCdp(SHARED_TOOLS.drag),
85
+ fill: onCdp(SHARED_TOOLS.fill),
86
+ fill_form: onCdp(SHARED_TOOLS.fill_form),
87
+ type_text: onCdp(SHARED_TOOLS.type_text),
88
+ press_key: onCdp(SHARED_TOOLS.press_key),
89
+ upload_file: onCdp(SHARED_TOOLS.upload_file),
81
90
  // screenshot + emulation (3)
82
- take_screenshot: takeScreenshot,
83
- emulate: emulate,
84
- resize_page: resizePage,
91
+ take_screenshot: onCdp(SHARED_TOOLS.take_screenshot),
92
+ emulate: onCdp(SHARED_TOOLS.emulate),
93
+ resize_page: onCdp(SHARED_TOOLS.resize_page),
85
94
  // dialogs (1)
86
- handle_dialog: handleDialog,
95
+ handle_dialog: onCdp(SHARED_TOOLS.handle_dialog),
87
96
  // console + network (4)
88
97
  list_console_messages: listConsoleMessages,
89
98
  get_console_message: getConsoleMessage,
@@ -96,12 +105,16 @@ export const TOOLS = {
96
105
  performance_trace: performanceTrace,
97
106
  // heap (1)
98
107
  take_heapsnapshot: takeHeapsnapshot,
99
- // lighthouse (1) — the only non-CDP tool
108
+ // lighthouse (1) : the only non-CDP tool
100
109
  lighthouse_audit: lighthouseAudit,
101
- // network mocking (3) — persistent per-target fake backend (toolkit addition, Fetch domain)
110
+ // network mocking (3) : persistent per-target fake backend (toolkit addition, Fetch domain)
102
111
  mock_request: mockRequest,
103
112
  list_mocks: listMocks,
104
113
  clear_mocks: clearMocks,
114
+ // tab leases (3) : opt-in ownership so many agents can share one browser
115
+ claim_page: onCdp(LEASE_TOOLS.claim_page),
116
+ release_page: onCdp(LEASE_TOOLS.release_page),
117
+ list_leases: onCdp(LEASE_TOOLS.list_leases),
105
118
  } satisfies Record<string, (args: never) => Promise<unknown>>;
106
119
 
107
120
  /** Canonical MCP tool name accepted by the registry. */
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The three lease tools, shaped like src/shared-tools.ts entries
3
+ * ((driver, args) => Promise<result>) so ONE implementation serves Chrome and
4
+ * Firefox, exactly as the other 20 unified tools do.
5
+ */
6
+ import type { BrowserDriver } from "./driver.ts";
7
+ import {
8
+ claimLease,
9
+ leaseTtlMs,
10
+ listLeases,
11
+ releaseLease,
12
+ type LeaseBackend,
13
+ type LeaseSummary,
14
+ type LeaseToken,
15
+ } from "./leases.ts";
16
+
17
+ class LeaseToolError extends Error {}
18
+
19
+ /** The driver's uid scheme is the one stable per-backend discriminator on the
20
+ * BrowserDriver surface ("cdp" for Chrome, "bidi" for Firefox). */
21
+ function backendOf(driver: BrowserDriver): LeaseBackend {
22
+ return driver.scheme === "bidi" ? "firefox" : "chrome";
23
+ }
24
+
25
+ export interface ClaimPageResult {
26
+ lease: LeaseToken;
27
+ targetId: string;
28
+ url: string;
29
+ label: string;
30
+ ttlMs: number;
31
+ expiresAt: number;
32
+ }
33
+
34
+ /**
35
+ * Claim a tab. With no targetId, opens a fresh tab first (optionally at `url`)
36
+ * and claims that, so "give me my own tab" is one call. With a targetId, claims
37
+ * an already-open tab.
38
+ */
39
+ export async function claimPage(
40
+ driver: BrowserDriver,
41
+ args: { targetId?: string; url?: string; label?: string; ttlMs?: number } = {},
42
+ ): Promise<ClaimPageResult> {
43
+ const backend = backendOf(driver);
44
+ const label = typeof args.label === "string" && args.label.length ? args.label : `pid-${process.pid}`;
45
+ const pages = await driver.listPages();
46
+ let targetId = args.targetId;
47
+ let url = args.url ?? "about:blank";
48
+ if (targetId === undefined || targetId === "") {
49
+ const page = await driver.newPage(args.url);
50
+ targetId = page.id;
51
+ url = page.url;
52
+ } else {
53
+ const existing = pages.find((p) => p.id === targetId);
54
+ if (!existing) throw new LeaseToolError(`claim_page: no page target with id '${targetId}'`);
55
+ url = existing.url;
56
+ }
57
+ const liveIds = [...pages.map((p) => p.id), targetId];
58
+ const { record, token } = await claimLease(backend, targetId, {
59
+ label,
60
+ ttlMs: typeof args.ttlMs === "number" && args.ttlMs > 0 ? args.ttlMs : leaseTtlMs(),
61
+ liveIds,
62
+ });
63
+ return {
64
+ lease: token,
65
+ targetId,
66
+ url,
67
+ label: record.label,
68
+ ttlMs: record.ttlMs,
69
+ expiresAt: record.lastUsedAt + record.ttlMs,
70
+ };
71
+ }
72
+
73
+ /** Release a held lease. Idempotent: an already-released or reclaimed lease
74
+ * reports released:false rather than throwing. */
75
+ export async function releasePage(
76
+ _driver: BrowserDriver,
77
+ args: { lease?: LeaseToken },
78
+ ): Promise<{ released: boolean }> {
79
+ if (typeof args.lease !== "string" || !args.lease.length) {
80
+ throw new LeaseToolError("release_page requires the 'lease' token returned by claim_page");
81
+ }
82
+ return releaseLease(args.lease);
83
+ }
84
+
85
+ /** Enumerate active leases for diagnosis. Requires no token by design. */
86
+ export async function listLeasesTool(
87
+ driver: BrowserDriver,
88
+ _args: Record<string, never> = {} as Record<string, never>,
89
+ ): Promise<{ leases: LeaseSummary[]; count: number }> {
90
+ const liveIds = (await driver.listPages()).map((p) => p.id);
91
+ // liveBackend scopes the target-gone test to the browser these ids came from,
92
+ // so leases held on the OTHER backend are not mislabeled as reclaimable.
93
+ const leases = await listLeases({ liveIds, liveBackend: backendOf(driver) });
94
+ return { leases, count: leases.length };
95
+ }
96
+
97
+ export const LEASE_TOOLS = {
98
+ claim_page: claimPage,
99
+ release_page: releasePage,
100
+ list_leases: listLeasesTool,
101
+ } satisfies Record<string, (driver: BrowserDriver, args: never) => Promise<unknown>>;