cdp-toolkit 1.1.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/CHANGELOG.md +76 -9
- package/CONTRACT.md +7 -5
- package/README.md +67 -30
- package/dist/cli.js +410 -61
- package/dist/index.js +370 -39
- package/dist/mcp.js +609 -72
- package/package.json +2 -2
- package/src/bidi/driver.ts +31 -3
- package/src/capabilities.ts +1 -1
- package/src/cli.ts +47 -22
- package/src/client.ts +25 -7
- package/src/driver.ts +1 -1
- package/src/firefox-tools.ts +6 -3
- package/src/index.ts +6 -1
- package/src/leases-tools.ts +101 -0
- package/src/leases.ts +533 -0
- package/src/manifest.ts +211 -13
- package/src/mcp.ts +27 -19
- package/src/shared-tools.ts +52 -6
- package/src/tools/console.ts +3 -3
- package/src/tools/evaluate.ts +2 -2
- package/src/tools/heap.ts +3 -3
- package/src/tools/lighthouse.ts +4 -4
- package/src/tools/navigation.ts +1 -1
- package/src/tools/network.ts +7 -7
- package/src/tools/network_mock.ts +8 -8
- package/src/tools/performance.ts +11 -11
- package/src/tools/recorder.ts +5 -5
- package/src/tools/snapshot.ts +2 -2
package/dist/mcp.js
CHANGED
|
@@ -1,5 +1,257 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/leases.ts
|
|
4
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
var DEFAULT_LEASE_TTL_MS = 900000;
|
|
9
|
+
function leaseDir() {
|
|
10
|
+
return process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
11
|
+
}
|
|
12
|
+
function leaseTtlMs() {
|
|
13
|
+
const raw = Number(process.env.CDP_LEASE_TTL_MS);
|
|
14
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_LEASE_TTL_MS;
|
|
15
|
+
}
|
|
16
|
+
function safeId(id) {
|
|
17
|
+
return id.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
18
|
+
}
|
|
19
|
+
function leaseFile(backend, targetId) {
|
|
20
|
+
return join(leaseDir(), `lease-${backend}-${safeId(targetId)}.json`);
|
|
21
|
+
}
|
|
22
|
+
function mintToken(backend, targetId, nonce) {
|
|
23
|
+
return `${backend}:${targetId}:${nonce}`;
|
|
24
|
+
}
|
|
25
|
+
function tokenParts(token) {
|
|
26
|
+
if (typeof token !== "string")
|
|
27
|
+
return;
|
|
28
|
+
const first = token.indexOf(":");
|
|
29
|
+
const last = token.lastIndexOf(":");
|
|
30
|
+
if (first <= 0 || last <= first)
|
|
31
|
+
return;
|
|
32
|
+
const backend = token.slice(0, first);
|
|
33
|
+
if (backend !== "chrome" && backend !== "firefox")
|
|
34
|
+
return;
|
|
35
|
+
const targetId = token.slice(first + 1, last);
|
|
36
|
+
const nonce = token.slice(last + 1);
|
|
37
|
+
if (!targetId.length || !/^[0-9a-f]{24}$/.test(nonce))
|
|
38
|
+
return;
|
|
39
|
+
return { backend, targetId, nonce };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class LeaseConflictError extends Error {
|
|
43
|
+
targetId;
|
|
44
|
+
holder;
|
|
45
|
+
constructor(message, targetId, holder) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.targetId = targetId;
|
|
48
|
+
this.holder = holder;
|
|
49
|
+
this.name = "LeaseConflictError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function readLease(backend, targetId) {
|
|
53
|
+
let raw;
|
|
54
|
+
try {
|
|
55
|
+
raw = await readFile(leaseFile(backend, targetId), "utf8");
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err.code === "ENOENT")
|
|
58
|
+
return;
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const rec = JSON.parse(raw);
|
|
63
|
+
if (typeof rec?.nonce === "string" && typeof rec?.pid === "number")
|
|
64
|
+
return rec;
|
|
65
|
+
} catch {}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
function isPidAlive(pid) {
|
|
69
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
70
|
+
return false;
|
|
71
|
+
try {
|
|
72
|
+
process.kill(pid, 0);
|
|
73
|
+
return true;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
return err.code === "EPERM";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function staleReason(rec, opts) {
|
|
79
|
+
if (!isPidAlive(rec.pid))
|
|
80
|
+
return "dead-pid";
|
|
81
|
+
if (opts.now - rec.lastUsedAt > rec.ttlMs)
|
|
82
|
+
return "expired";
|
|
83
|
+
if (opts.liveIds && !opts.liveIds.includes(rec.targetId))
|
|
84
|
+
return "target-gone";
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
async function claimLease(backend, targetId, opts) {
|
|
88
|
+
const now = opts.now ?? Date.now();
|
|
89
|
+
await mkdir(leaseDir(), { recursive: true });
|
|
90
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
91
|
+
const record = {
|
|
92
|
+
backend,
|
|
93
|
+
targetId,
|
|
94
|
+
nonce: randomBytes(12).toString("hex"),
|
|
95
|
+
pid: process.pid,
|
|
96
|
+
label: opts.label,
|
|
97
|
+
createdAt: now,
|
|
98
|
+
lastUsedAt: now,
|
|
99
|
+
ttlMs: opts.ttlMs ?? leaseTtlMs()
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
await writeFile(leaseFile(backend, targetId), JSON.stringify(record), { flag: "wx" });
|
|
103
|
+
return { token: mintToken(backend, targetId, record.nonce), record };
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err.code !== "EEXIST")
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
const existing = await readLease(backend, targetId);
|
|
109
|
+
if (!existing)
|
|
110
|
+
continue;
|
|
111
|
+
const stale = staleReason(existing, { now, liveIds: opts.liveIds });
|
|
112
|
+
if (!stale) {
|
|
113
|
+
throw new LeaseConflictError(`target '${targetId}' is already leased by '${existing.label}' (pid ${existing.pid}); call release_page with that lease's token, or list_leases to inspect it`, targetId, existing.label);
|
|
114
|
+
}
|
|
115
|
+
await unlink(leaseFile(backend, targetId)).catch(() => {
|
|
116
|
+
return;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
throw new LeaseConflictError(`target '${targetId}' could not be claimed after 3 attempts: another process is claiming and releasing it concurrently`, targetId);
|
|
120
|
+
}
|
|
121
|
+
async function releaseLease(token) {
|
|
122
|
+
const parts = tokenParts(token);
|
|
123
|
+
if (!parts)
|
|
124
|
+
return { released: false };
|
|
125
|
+
const rec = await readLease(parts.backend, parts.targetId);
|
|
126
|
+
if (!rec || rec.nonce !== parts.nonce)
|
|
127
|
+
return { released: false };
|
|
128
|
+
await unlink(leaseFile(parts.backend, parts.targetId)).catch(() => {
|
|
129
|
+
return;
|
|
130
|
+
});
|
|
131
|
+
return { released: true };
|
|
132
|
+
}
|
|
133
|
+
async function releaseLeaseFor(backend, targetId) {
|
|
134
|
+
if (!await readLease(backend, targetId))
|
|
135
|
+
return { released: false };
|
|
136
|
+
await unlink(leaseFile(backend, targetId)).catch(() => {
|
|
137
|
+
return;
|
|
138
|
+
});
|
|
139
|
+
return { released: true };
|
|
140
|
+
}
|
|
141
|
+
var leaseScope = new AsyncLocalStorage;
|
|
142
|
+
function withLeaseScope(lease, fn) {
|
|
143
|
+
return leaseScope.run(lease, fn);
|
|
144
|
+
}
|
|
145
|
+
function currentLease() {
|
|
146
|
+
return leaseScope.getStore();
|
|
147
|
+
}
|
|
148
|
+
function leaseFromArgs(args) {
|
|
149
|
+
if (!args || typeof args !== "object")
|
|
150
|
+
return;
|
|
151
|
+
const lease = args.lease;
|
|
152
|
+
return typeof lease === "string" && lease.length > 0 ? lease : undefined;
|
|
153
|
+
}
|
|
154
|
+
async function touchLease(rec, now = Date.now()) {
|
|
155
|
+
const current = await readLease(rec.backend, rec.targetId).catch(() => {
|
|
156
|
+
return;
|
|
157
|
+
});
|
|
158
|
+
if (!current || current.nonce !== rec.nonce)
|
|
159
|
+
return;
|
|
160
|
+
await writeFile(leaseFile(current.backend, current.targetId), JSON.stringify({ ...current, lastUsedAt: now }), "utf8").catch(() => {
|
|
161
|
+
return;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function describeTarget(targetId, ctx) {
|
|
165
|
+
const url = ctx.url ? ` (url ${ctx.url})` : ctx.title ? ` (title ${JSON.stringify(ctx.title)})` : "";
|
|
166
|
+
return `target '${targetId}'${url}`;
|
|
167
|
+
}
|
|
168
|
+
async function assertLeaseOk(backend, targetId, ctx = {}) {
|
|
169
|
+
const token = ctx.lease ?? currentLease();
|
|
170
|
+
const now = ctx.now ?? Date.now();
|
|
171
|
+
const rec = await readLease(backend, targetId);
|
|
172
|
+
const held = rec && !staleReason(rec, { now, liveIds: ctx.liveIds }) ? rec : undefined;
|
|
173
|
+
const where = describeTarget(targetId, ctx);
|
|
174
|
+
if (token === undefined) {
|
|
175
|
+
if (!held)
|
|
176
|
+
return;
|
|
177
|
+
throw new LeaseConflictError(`${where} is leased by '${held.label}' (pid ${held.pid}). Pass that lease's token as the 'lease' argument, or call release_page to free it. list_leases shows every active lease.`, targetId, held.label);
|
|
178
|
+
}
|
|
179
|
+
const parts = tokenParts(token);
|
|
180
|
+
if (!parts) {
|
|
181
|
+
throw new LeaseConflictError(`malformed lease token for ${where}. Pass back the token claim_page returned, never a constructed one.`, targetId);
|
|
182
|
+
}
|
|
183
|
+
if (parts.backend !== backend || parts.targetId !== targetId) {
|
|
184
|
+
if (!held)
|
|
185
|
+
return;
|
|
186
|
+
throw new LeaseConflictError(`the lease token you passed is for ${parts.backend} target '${parts.targetId}', not ${where}, which is leased by '${held.label}'. Resolve the tab you actually hold, or claim this one.`, targetId, held.label);
|
|
187
|
+
}
|
|
188
|
+
if (!held) {
|
|
189
|
+
throw new LeaseConflictError(`${where} is not leased any more: the token you passed belongs to a lease that was released or reclaimed. Call claim_page again to take the tab.`, targetId);
|
|
190
|
+
}
|
|
191
|
+
if (parts.nonce !== held.nonce) {
|
|
192
|
+
throw new LeaseConflictError(`${where} was reclaimed and is now leased by '${held.label}' (pid ${held.pid}). Your token was invalidated by that reclamation. Call claim_page again if you still need this tab.`, targetId, held.label);
|
|
193
|
+
}
|
|
194
|
+
await touchLease(held, now);
|
|
195
|
+
}
|
|
196
|
+
var LEASE_FILE_RE = /^lease-(chrome|firefox)-(.+)\.json$/;
|
|
197
|
+
async function listLeases(opts = {}) {
|
|
198
|
+
const now = opts.now ?? Date.now();
|
|
199
|
+
let names = [];
|
|
200
|
+
try {
|
|
201
|
+
names = await readdir(leaseDir());
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (err.code === "ENOENT")
|
|
204
|
+
return [];
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
const out = [];
|
|
208
|
+
for (const name of names) {
|
|
209
|
+
const parts = LEASE_FILE_RE.exec(name);
|
|
210
|
+
if (!parts)
|
|
211
|
+
continue;
|
|
212
|
+
const fromName = {
|
|
213
|
+
backend: parts[1],
|
|
214
|
+
targetId: parts[2],
|
|
215
|
+
label: "",
|
|
216
|
+
pid: 0,
|
|
217
|
+
createdAt: 0,
|
|
218
|
+
lastUsedAt: 0,
|
|
219
|
+
ttlMs: 0,
|
|
220
|
+
pidAlive: false,
|
|
221
|
+
stale: false
|
|
222
|
+
};
|
|
223
|
+
let rec;
|
|
224
|
+
try {
|
|
225
|
+
rec = JSON.parse(await readFile(join(leaseDir(), name), "utf8"));
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const code = err.code;
|
|
228
|
+
if (code === "ENOENT")
|
|
229
|
+
continue;
|
|
230
|
+
out.push({ ...fromName, unreadable: code ?? "unparseable" });
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (typeof rec?.nonce !== "string" || typeof rec?.pid !== "number") {
|
|
234
|
+
out.push({ ...fromName, unreadable: "unparseable" });
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
out.push({
|
|
238
|
+
backend: rec.backend,
|
|
239
|
+
targetId: rec.targetId,
|
|
240
|
+
label: rec.label,
|
|
241
|
+
pid: rec.pid,
|
|
242
|
+
createdAt: rec.createdAt,
|
|
243
|
+
lastUsedAt: rec.lastUsedAt,
|
|
244
|
+
ttlMs: rec.ttlMs,
|
|
245
|
+
pidAlive: isPidAlive(rec.pid),
|
|
246
|
+
stale: staleReason(rec, {
|
|
247
|
+
now,
|
|
248
|
+
liveIds: opts.liveBackend === undefined || opts.liveBackend === rec.backend ? opts.liveIds : undefined
|
|
249
|
+
})
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
|
|
3
255
|
// src/client.ts
|
|
4
256
|
var BASE = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
|
|
5
257
|
var DEFAULT_TIMEOUT_MS = Number(process.env.CDP_TIMEOUT_MS ?? 15000);
|
|
@@ -153,8 +405,13 @@ async function browserWsUrl() {
|
|
|
153
405
|
const v = await httpJson("/json/version");
|
|
154
406
|
return v.webSocketDebuggerUrl;
|
|
155
407
|
}
|
|
156
|
-
async function resolveTarget(selector) {
|
|
408
|
+
async function resolveTarget(selector, opts = {}) {
|
|
157
409
|
const targets = await listTargets();
|
|
410
|
+
const hit = pickTarget(targets, selector);
|
|
411
|
+
await assertLeaseOk("chrome", hit.id, { lease: opts.lease, url: hit.url, title: hit.title });
|
|
412
|
+
return hit;
|
|
413
|
+
}
|
|
414
|
+
function pickTarget(targets, selector) {
|
|
158
415
|
const pages = targets.filter((t) => t.type === "page");
|
|
159
416
|
if (!selector || selector === "active") {
|
|
160
417
|
if (!pages.length)
|
|
@@ -190,7 +447,7 @@ async function openBrowser(opts = {}) {
|
|
|
190
447
|
return new CdpConnection(await browserWsUrl(), opts).connect();
|
|
191
448
|
}
|
|
192
449
|
async function openPage(selector, opts = {}) {
|
|
193
|
-
const target = await resolveTarget(selector);
|
|
450
|
+
const target = await resolveTarget(selector, { lease: opts.lease });
|
|
194
451
|
const conn = await new CdpConnection(target.webSocketDebuggerUrl, opts).connect();
|
|
195
452
|
return { conn, target };
|
|
196
453
|
}
|
|
@@ -1071,9 +1328,63 @@ function createCdpDriver() {
|
|
|
1071
1328
|
return new CdpBrowserDriver;
|
|
1072
1329
|
}
|
|
1073
1330
|
|
|
1331
|
+
// src/leases-tools.ts
|
|
1332
|
+
class LeaseToolError extends Error {
|
|
1333
|
+
}
|
|
1334
|
+
function backendOf(driver) {
|
|
1335
|
+
return driver.scheme === "bidi" ? "firefox" : "chrome";
|
|
1336
|
+
}
|
|
1337
|
+
async function claimPage(driver, args = {}) {
|
|
1338
|
+
const backend = backendOf(driver);
|
|
1339
|
+
const label = typeof args.label === "string" && args.label.length ? args.label : `pid-${process.pid}`;
|
|
1340
|
+
const pages = await driver.listPages();
|
|
1341
|
+
let targetId = args.targetId;
|
|
1342
|
+
let url = args.url ?? "about:blank";
|
|
1343
|
+
if (targetId === undefined || targetId === "") {
|
|
1344
|
+
const page = await driver.newPage(args.url);
|
|
1345
|
+
targetId = page.id;
|
|
1346
|
+
url = page.url;
|
|
1347
|
+
} else {
|
|
1348
|
+
const existing = pages.find((p) => p.id === targetId);
|
|
1349
|
+
if (!existing)
|
|
1350
|
+
throw new LeaseToolError(`claim_page: no page target with id '${targetId}'`);
|
|
1351
|
+
url = existing.url;
|
|
1352
|
+
}
|
|
1353
|
+
const liveIds = [...pages.map((p) => p.id), targetId];
|
|
1354
|
+
const { record, token } = await claimLease(backend, targetId, {
|
|
1355
|
+
label,
|
|
1356
|
+
ttlMs: typeof args.ttlMs === "number" && args.ttlMs > 0 ? args.ttlMs : leaseTtlMs(),
|
|
1357
|
+
liveIds
|
|
1358
|
+
});
|
|
1359
|
+
return {
|
|
1360
|
+
lease: token,
|
|
1361
|
+
targetId,
|
|
1362
|
+
url,
|
|
1363
|
+
label: record.label,
|
|
1364
|
+
ttlMs: record.ttlMs,
|
|
1365
|
+
expiresAt: record.lastUsedAt + record.ttlMs
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
async function releasePage(_driver, args) {
|
|
1369
|
+
if (typeof args.lease !== "string" || !args.lease.length) {
|
|
1370
|
+
throw new LeaseToolError("release_page requires the 'lease' token returned by claim_page");
|
|
1371
|
+
}
|
|
1372
|
+
return releaseLease(args.lease);
|
|
1373
|
+
}
|
|
1374
|
+
async function listLeasesTool(driver, _args = {}) {
|
|
1375
|
+
const liveIds = (await driver.listPages()).map((p) => p.id);
|
|
1376
|
+
const leases = await listLeases({ liveIds, liveBackend: backendOf(driver) });
|
|
1377
|
+
return { leases, count: leases.length };
|
|
1378
|
+
}
|
|
1379
|
+
var LEASE_TOOLS = {
|
|
1380
|
+
claim_page: claimPage,
|
|
1381
|
+
release_page: releasePage,
|
|
1382
|
+
list_leases: listLeasesTool
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1074
1385
|
// src/shared-tools.ts
|
|
1075
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
1076
|
-
import { join } from "node:path";
|
|
1386
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1387
|
+
import { join as join2 } from "node:path";
|
|
1077
1388
|
var ARTIFACT_DIR = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
1078
1389
|
var STATE_DIR = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
|
|
1079
1390
|
|
|
@@ -1082,6 +1393,9 @@ class SharedToolError extends Error {
|
|
|
1082
1393
|
function target3(p) {
|
|
1083
1394
|
return { id: p.id, url: p.url, title: p.title };
|
|
1084
1395
|
}
|
|
1396
|
+
function backendOf2(driver) {
|
|
1397
|
+
return driver.scheme === "bidi" ? "firefox" : "chrome";
|
|
1398
|
+
}
|
|
1085
1399
|
async function withPage2(driver, target, fn) {
|
|
1086
1400
|
const page = await driver.page(target);
|
|
1087
1401
|
try {
|
|
@@ -1092,6 +1406,11 @@ async function withPage2(driver, target, fn) {
|
|
|
1092
1406
|
}
|
|
1093
1407
|
async function resolvePage(driver, selector) {
|
|
1094
1408
|
const pages = await driver.listPages();
|
|
1409
|
+
const hit = await pickPage(driver, pages, selector);
|
|
1410
|
+
await assertLeaseOk(backendOf2(driver), hit.id, { url: hit.url, title: hit.title });
|
|
1411
|
+
return hit;
|
|
1412
|
+
}
|
|
1413
|
+
async function pickPage(driver, pages, selector) {
|
|
1095
1414
|
if (selector === undefined || selector === "active") {
|
|
1096
1415
|
if (!pages.length)
|
|
1097
1416
|
throw new SharedToolError("no page targets open");
|
|
@@ -1139,18 +1458,27 @@ async function listPages(driver, args = {}) {
|
|
|
1139
1458
|
}
|
|
1140
1459
|
async function newPage(driver, args = {}) {
|
|
1141
1460
|
const p = await driver.newPage(args.url);
|
|
1142
|
-
|
|
1461
|
+
if (args.claim !== true)
|
|
1462
|
+
return { targetId: p.id, url: p.url };
|
|
1463
|
+
const { record, token } = await claimLease(backendOf2(driver), p.id, {
|
|
1464
|
+
label: typeof args.label === "string" && args.label.length ? args.label : `pid-${process.pid}`,
|
|
1465
|
+
ttlMs: typeof args.ttlMs === "number" && args.ttlMs > 0 ? args.ttlMs : leaseTtlMs()
|
|
1466
|
+
});
|
|
1467
|
+
return { targetId: p.id, url: p.url, lease: token, label: record.label, expiresAt: record.lastUsedAt + record.ttlMs };
|
|
1143
1468
|
}
|
|
1144
1469
|
async function closePage(driver, args) {
|
|
1145
1470
|
if (args.target === undefined || args.target === "")
|
|
1146
1471
|
throw new SharedToolError("close_page requires an explicit target; refusing to guess which page to close");
|
|
1147
1472
|
const p = await resolvePage(driver, args.target);
|
|
1148
1473
|
const res = await driver.closePage(p.id);
|
|
1149
|
-
|
|
1474
|
+
if (!res.success)
|
|
1475
|
+
return { closed: p.id, success: false, leaseReleased: false };
|
|
1476
|
+
const released = await releaseLeaseFor(backendOf2(driver), p.id);
|
|
1477
|
+
return { closed: p.id, success: true, ...released.released ? { leaseReleased: true } : {} };
|
|
1150
1478
|
}
|
|
1151
1479
|
async function writeSelectedFile(id) {
|
|
1152
|
-
await
|
|
1153
|
-
await
|
|
1480
|
+
await mkdir2(STATE_DIR, { recursive: true });
|
|
1481
|
+
await writeFile2(join2(STATE_DIR, "selected"), id, "utf8");
|
|
1154
1482
|
}
|
|
1155
1483
|
async function selectPage(driver, args) {
|
|
1156
1484
|
if (args.target === undefined || args.target === "")
|
|
@@ -1305,10 +1633,10 @@ async function takeScreenshot(driver, args = {}) {
|
|
|
1305
1633
|
const { data, format: outFormat } = await page.screenshot({ format, quality: args.quality, fullPage: args.fullPage, ...clip ? { clip } : {} });
|
|
1306
1634
|
if (data.byteLength === 0)
|
|
1307
1635
|
throw new SharedToolError("captureScreenshot returned empty data");
|
|
1308
|
-
await
|
|
1636
|
+
await mkdir2(ARTIFACT_DIR, { recursive: true });
|
|
1309
1637
|
const ext = outFormat === "jpeg" ? "jpg" : "png";
|
|
1310
|
-
const path = args.savePath ??
|
|
1311
|
-
await
|
|
1638
|
+
const path = args.savePath ?? join2(ARTIFACT_DIR, `screenshot-${page.info.id.slice(0, 8)}-${stamp()}.${ext}`);
|
|
1639
|
+
await writeFile2(path, Buffer.from(data));
|
|
1312
1640
|
const result = {
|
|
1313
1641
|
path,
|
|
1314
1642
|
bytes: data.byteLength,
|
|
@@ -1371,10 +1699,10 @@ var SHARED_TOOLS = {
|
|
|
1371
1699
|
};
|
|
1372
1700
|
|
|
1373
1701
|
// src/tools/console.ts
|
|
1374
|
-
import { readFile } from "node:fs/promises";
|
|
1702
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1375
1703
|
|
|
1376
1704
|
// src/tools/recorder.ts
|
|
1377
|
-
import { mkdir as
|
|
1705
|
+
import { mkdir as mkdir3, appendFile, writeFile as writeFile3, copyFile } from "node:fs/promises";
|
|
1378
1706
|
var ARTIFACT_DIR2 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
1379
1707
|
function recFile(targetId) {
|
|
1380
1708
|
return `${ARTIFACT_DIR2}/rec-${targetId}.jsonl`;
|
|
@@ -1395,11 +1723,11 @@ var CONSOLE_EVENTS = ["Runtime.consoleAPICalled", "Runtime.exceptionThrown", "Lo
|
|
|
1395
1723
|
async function startRecorder(target, opts = {}) {
|
|
1396
1724
|
const network = opts.network ?? true;
|
|
1397
1725
|
const consoleCap = opts.console ?? true;
|
|
1398
|
-
await
|
|
1726
|
+
await mkdir3(ARTIFACT_DIR2, { recursive: true });
|
|
1399
1727
|
const { conn, target: resolved } = await openPage(target, { timeoutMs: opts.timeoutMs });
|
|
1400
1728
|
const file = opts.file ?? recFile(resolved.id);
|
|
1401
1729
|
if (opts.truncate) {
|
|
1402
|
-
await
|
|
1730
|
+
await writeFile3(file, "");
|
|
1403
1731
|
}
|
|
1404
1732
|
let chain = Promise.resolve();
|
|
1405
1733
|
let dropped = 0;
|
|
@@ -1541,7 +1869,7 @@ function lineToEntry(line, index) {
|
|
|
1541
1869
|
async function readConsoleEntries(file) {
|
|
1542
1870
|
let raw;
|
|
1543
1871
|
try {
|
|
1544
|
-
raw = await
|
|
1872
|
+
raw = await readFile2(file, "utf8");
|
|
1545
1873
|
} catch {
|
|
1546
1874
|
return [];
|
|
1547
1875
|
}
|
|
@@ -1597,7 +1925,7 @@ async function getConsoleMessage(args = {}) {
|
|
|
1597
1925
|
}
|
|
1598
1926
|
|
|
1599
1927
|
// src/tools/network.ts
|
|
1600
|
-
import { readFile as
|
|
1928
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1601
1929
|
var DEFAULT_CAPTURE_MS2 = 2500;
|
|
1602
1930
|
function asHeaders(v) {
|
|
1603
1931
|
if (!v || typeof v !== "object")
|
|
@@ -1664,7 +1992,7 @@ function correlate(lines) {
|
|
|
1664
1992
|
async function readNetworkRequests(file) {
|
|
1665
1993
|
let raw;
|
|
1666
1994
|
try {
|
|
1667
|
-
raw = await
|
|
1995
|
+
raw = await readFile3(file, "utf8");
|
|
1668
1996
|
} catch {
|
|
1669
1997
|
return [];
|
|
1670
1998
|
}
|
|
@@ -1746,8 +2074,8 @@ async function getNetworkRequest(args = {}) {
|
|
|
1746
2074
|
}
|
|
1747
2075
|
|
|
1748
2076
|
// src/tools/performance.ts
|
|
1749
|
-
import { mkdir as
|
|
1750
|
-
import { join as
|
|
2077
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, stat, unlink as unlink2 } from "node:fs/promises";
|
|
2078
|
+
import { join as join3 } from "node:path";
|
|
1751
2079
|
var ARTIFACT_DIR3 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
1752
2080
|
var STATE_DIR2 = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
|
|
1753
2081
|
var DEFAULT_CATEGORIES = [
|
|
@@ -1765,7 +2093,7 @@ function shortId(id) {
|
|
|
1765
2093
|
return id.slice(0, 8);
|
|
1766
2094
|
}
|
|
1767
2095
|
function stateFilePath() {
|
|
1768
|
-
return
|
|
2096
|
+
return join3(STATE_DIR2, "trace-state.json");
|
|
1769
2097
|
}
|
|
1770
2098
|
function bufferTraceData(conn, sink) {
|
|
1771
2099
|
return conn.on("Tracing.dataCollected", (params) => {
|
|
@@ -1787,10 +2115,10 @@ async function endTrace(conn, sink, timeoutMs = 30000) {
|
|
|
1787
2115
|
return sink;
|
|
1788
2116
|
}
|
|
1789
2117
|
async function writeTrace(events, targetId) {
|
|
1790
|
-
await
|
|
1791
|
-
const path =
|
|
2118
|
+
await mkdir4(ARTIFACT_DIR3, { recursive: true });
|
|
2119
|
+
const path = join3(ARTIFACT_DIR3, `trace-${shortId(targetId)}-${stamp2()}.json`);
|
|
1792
2120
|
const body = JSON.stringify({ traceEvents: events, metadata: { source: "cdp-toolkit" } });
|
|
1793
|
-
await
|
|
2121
|
+
await writeFile4(path, body, "utf8");
|
|
1794
2122
|
const { size } = await stat(path);
|
|
1795
2123
|
return { path, bytes: size };
|
|
1796
2124
|
}
|
|
@@ -1810,7 +2138,7 @@ async function performanceStartTrace(args = {}) {
|
|
|
1810
2138
|
throw err;
|
|
1811
2139
|
}
|
|
1812
2140
|
liveTraces.set(target.id, { conn, target, events, startedAt: Date.now() });
|
|
1813
|
-
await
|
|
2141
|
+
await mkdir4(STATE_DIR2, { recursive: true });
|
|
1814
2142
|
const state = {
|
|
1815
2143
|
targetId: target.id,
|
|
1816
2144
|
url: target.url,
|
|
@@ -1818,7 +2146,7 @@ async function performanceStartTrace(args = {}) {
|
|
|
1818
2146
|
pid: process.pid,
|
|
1819
2147
|
categories
|
|
1820
2148
|
};
|
|
1821
|
-
await
|
|
2149
|
+
await writeFile4(stateFilePath(), JSON.stringify(state), "utf8");
|
|
1822
2150
|
return {
|
|
1823
2151
|
target: { id: target.id, url: target.url, title: target.title },
|
|
1824
2152
|
categories,
|
|
@@ -1831,7 +2159,7 @@ async function performanceStopTrace(args = {}) {
|
|
|
1831
2159
|
if (!entry) {
|
|
1832
2160
|
let hint = "";
|
|
1833
2161
|
try {
|
|
1834
|
-
const raw = await
|
|
2162
|
+
const raw = await readFile4(stateFilePath(), "utf8");
|
|
1835
2163
|
const state = JSON.parse(raw);
|
|
1836
2164
|
hint = state.pid === process.pid ? ` (state file lists target ${shortId(state.targetId)} but its live connection is gone)` : ` (trace was started by a DIFFERENT process pid=${state.pid}; live recordings cannot cross process boundaries)`;
|
|
1837
2165
|
} catch {}
|
|
@@ -1843,7 +2171,7 @@ async function performanceStopTrace(args = {}) {
|
|
|
1843
2171
|
} finally {
|
|
1844
2172
|
conn.close();
|
|
1845
2173
|
liveTraces.delete(target.id);
|
|
1846
|
-
await
|
|
2174
|
+
await unlink2(stateFilePath()).catch(() => {});
|
|
1847
2175
|
}
|
|
1848
2176
|
const written = await writeTrace(events, target.id);
|
|
1849
2177
|
const metrics = analyzeEvents(events);
|
|
@@ -1961,7 +2289,7 @@ async function performanceAnalyzeInsight(args = {}) {
|
|
|
1961
2289
|
}
|
|
1962
2290
|
let raw;
|
|
1963
2291
|
try {
|
|
1964
|
-
raw = await
|
|
2292
|
+
raw = await readFile4(args.tracePath, "utf8");
|
|
1965
2293
|
} catch (err) {
|
|
1966
2294
|
throw new CdpError(`cannot read trace file '${args.tracePath}': ${err.message}`);
|
|
1967
2295
|
}
|
|
@@ -1978,8 +2306,8 @@ async function performanceAnalyzeInsight(args = {}) {
|
|
|
1978
2306
|
}
|
|
1979
2307
|
|
|
1980
2308
|
// src/tools/heap.ts
|
|
1981
|
-
import { mkdir as
|
|
1982
|
-
import { join as
|
|
2309
|
+
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
2310
|
+
import { join as join4 } from "node:path";
|
|
1983
2311
|
var ARTIFACT_DIR4 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
1984
2312
|
function stamp3(targetId) {
|
|
1985
2313
|
const short = targetId.slice(0, 8);
|
|
@@ -2004,9 +2332,9 @@ async function takeHeapsnapshot(args = {}) {
|
|
|
2004
2332
|
if (snapshot.length === 0) {
|
|
2005
2333
|
throw new Error("HeapProfiler.takeHeapSnapshot produced no chunks");
|
|
2006
2334
|
}
|
|
2007
|
-
await
|
|
2008
|
-
const path = args.savePath ? args.savePath.startsWith("/") ? args.savePath :
|
|
2009
|
-
await
|
|
2335
|
+
await mkdir5(ARTIFACT_DIR4, { recursive: true });
|
|
2336
|
+
const path = args.savePath ? args.savePath.startsWith("/") ? args.savePath : join4(ARTIFACT_DIR4, args.savePath) : join4(ARTIFACT_DIR4, `take_heapsnapshot-${stamp3(target.id)}.heapsnapshot`);
|
|
2337
|
+
await writeFile5(path, snapshot, "utf8");
|
|
2010
2338
|
const bytes = Buffer.byteLength(snapshot, "utf8");
|
|
2011
2339
|
return {
|
|
2012
2340
|
path,
|
|
@@ -2019,7 +2347,7 @@ async function takeHeapsnapshot(args = {}) {
|
|
|
2019
2347
|
|
|
2020
2348
|
// src/tools/lighthouse.ts
|
|
2021
2349
|
import { spawn } from "node:child_process";
|
|
2022
|
-
import { mkdir as
|
|
2350
|
+
import { mkdir as mkdir6, readFile as readFile5, stat as stat2 } from "node:fs/promises";
|
|
2023
2351
|
var ARTIFACT_DIR5 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
|
|
2024
2352
|
function cdpPort() {
|
|
2025
2353
|
const base = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
|
|
@@ -2080,7 +2408,7 @@ async function lighthouseAudit(args) {
|
|
|
2080
2408
|
const url = args.url.trim();
|
|
2081
2409
|
const timeoutMs = args.timeoutMs ?? 120000;
|
|
2082
2410
|
const port = cdpPort();
|
|
2083
|
-
await
|
|
2411
|
+
await mkdir6(ARTIFACT_DIR5, { recursive: true });
|
|
2084
2412
|
const outPath = args.savePath ?? `${ARTIFACT_DIR5}/lighthouse-${shortHost(url)}-${stamp4()}.json`;
|
|
2085
2413
|
const formFactor = args.formFactor ?? "desktop";
|
|
2086
2414
|
const lhArgs = [
|
|
@@ -2109,7 +2437,7 @@ async function lighthouseAudit(args) {
|
|
|
2109
2437
|
}
|
|
2110
2438
|
let raw;
|
|
2111
2439
|
try {
|
|
2112
|
-
raw = await
|
|
2440
|
+
raw = await readFile5(outPath, "utf8");
|
|
2113
2441
|
} catch {
|
|
2114
2442
|
raw = undefined;
|
|
2115
2443
|
}
|
|
@@ -2117,7 +2445,7 @@ async function lighthouseAudit(args) {
|
|
|
2117
2445
|
if (res.signal === "SIGKILL") {
|
|
2118
2446
|
throw new CdpError(`lighthouse_audit timed out after ${timeoutMs}ms (no report written). stderr: ${truncate(res.stderr)}`);
|
|
2119
2447
|
}
|
|
2120
|
-
const hint = /not found|could not determine executable|npm ERR|E404|ERR_MODULE/i.test(res.stderr) ? " The Lighthouse CLI could not be fetched/installed via npx
|
|
2448
|
+
const hint = /not found|could not determine executable|npm ERR|E404|ERR_MODULE/i.test(res.stderr) ? " The Lighthouse CLI could not be fetched/installed via npx; ensure network access and that 'lighthouse' is installable." : "";
|
|
2121
2449
|
throw new CdpError(`lighthouse_audit produced no report (exit code ${res.code}).${hint} stderr: ${truncate(res.stderr)}`);
|
|
2122
2450
|
}
|
|
2123
2451
|
let report;
|
|
@@ -2191,7 +2519,10 @@ var TOOLS = {
|
|
|
2191
2519
|
lighthouse_audit: lighthouseAudit,
|
|
2192
2520
|
mock_request: mockRequest,
|
|
2193
2521
|
list_mocks: listMocks,
|
|
2194
|
-
clear_mocks: clearMocks
|
|
2522
|
+
clear_mocks: clearMocks,
|
|
2523
|
+
claim_page: onCdp(LEASE_TOOLS.claim_page),
|
|
2524
|
+
release_page: onCdp(LEASE_TOOLS.release_page),
|
|
2525
|
+
list_leases: onCdp(LEASE_TOOLS.list_leases)
|
|
2195
2526
|
};
|
|
2196
2527
|
var TOOL_NAMES = Object.keys(TOOLS);
|
|
2197
2528
|
|
|
@@ -2208,6 +2539,10 @@ var MANIFEST = [
|
|
|
2208
2539
|
inputSchema: {
|
|
2209
2540
|
type: "object",
|
|
2210
2541
|
properties: {
|
|
2542
|
+
lease: {
|
|
2543
|
+
type: "string",
|
|
2544
|
+
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."
|
|
2545
|
+
},
|
|
2211
2546
|
all: {
|
|
2212
2547
|
type: "boolean",
|
|
2213
2548
|
description: "Include non-page targets (service/shared workers, background pages) when true; otherwise only page-type tabs are listed."
|
|
@@ -2223,9 +2558,25 @@ var MANIFEST = [
|
|
|
2223
2558
|
inputSchema: {
|
|
2224
2559
|
type: "object",
|
|
2225
2560
|
properties: {
|
|
2561
|
+
lease: {
|
|
2562
|
+
type: "string",
|
|
2563
|
+
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."
|
|
2564
|
+
},
|
|
2226
2565
|
url: {
|
|
2227
2566
|
type: "string",
|
|
2228
2567
|
description: "URL to open in the new tab; defaults to about:blank."
|
|
2568
|
+
},
|
|
2569
|
+
claim: {
|
|
2570
|
+
type: "boolean",
|
|
2571
|
+
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."
|
|
2572
|
+
},
|
|
2573
|
+
label: {
|
|
2574
|
+
type: "string",
|
|
2575
|
+
description: "Agent label recorded on the lease when claim:true, surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
|
|
2576
|
+
},
|
|
2577
|
+
ttlMs: {
|
|
2578
|
+
type: "number",
|
|
2579
|
+
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."
|
|
2229
2580
|
}
|
|
2230
2581
|
},
|
|
2231
2582
|
required: [],
|
|
@@ -2238,6 +2589,10 @@ var MANIFEST = [
|
|
|
2238
2589
|
inputSchema: {
|
|
2239
2590
|
type: "object",
|
|
2240
2591
|
properties: {
|
|
2592
|
+
lease: {
|
|
2593
|
+
type: "string",
|
|
2594
|
+
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."
|
|
2595
|
+
},
|
|
2241
2596
|
target: {
|
|
2242
2597
|
type: "string",
|
|
2243
2598
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2255,6 +2610,10 @@ var MANIFEST = [
|
|
|
2255
2610
|
inputSchema: {
|
|
2256
2611
|
type: "object",
|
|
2257
2612
|
properties: {
|
|
2613
|
+
lease: {
|
|
2614
|
+
type: "string",
|
|
2615
|
+
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."
|
|
2616
|
+
},
|
|
2258
2617
|
target: {
|
|
2259
2618
|
type: "string",
|
|
2260
2619
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2272,6 +2631,10 @@ var MANIFEST = [
|
|
|
2272
2631
|
inputSchema: {
|
|
2273
2632
|
type: "object",
|
|
2274
2633
|
properties: {
|
|
2634
|
+
lease: {
|
|
2635
|
+
type: "string",
|
|
2636
|
+
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."
|
|
2637
|
+
},
|
|
2275
2638
|
target: {
|
|
2276
2639
|
type: "string",
|
|
2277
2640
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2306,10 +2669,14 @@ var MANIFEST = [
|
|
|
2306
2669
|
},
|
|
2307
2670
|
{
|
|
2308
2671
|
name: "wait_for",
|
|
2309
|
-
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
|
|
2672
|
+
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}.",
|
|
2310
2673
|
inputSchema: {
|
|
2311
2674
|
type: "object",
|
|
2312
2675
|
properties: {
|
|
2676
|
+
lease: {
|
|
2677
|
+
type: "string",
|
|
2678
|
+
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."
|
|
2679
|
+
},
|
|
2313
2680
|
target: {
|
|
2314
2681
|
type: "string",
|
|
2315
2682
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2339,6 +2706,10 @@ var MANIFEST = [
|
|
|
2339
2706
|
inputSchema: {
|
|
2340
2707
|
type: "object",
|
|
2341
2708
|
properties: {
|
|
2709
|
+
lease: {
|
|
2710
|
+
type: "string",
|
|
2711
|
+
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."
|
|
2712
|
+
},
|
|
2342
2713
|
target: {
|
|
2343
2714
|
type: "string",
|
|
2344
2715
|
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."
|
|
@@ -2364,10 +2735,14 @@ var MANIFEST = [
|
|
|
2364
2735
|
},
|
|
2365
2736
|
{
|
|
2366
2737
|
name: "take_snapshot",
|
|
2367
|
-
description: "Capture the page's accessibility tree (Accessibility.getFullAXTree) as a compact indented text tree where each line is prefixed with [uid]
|
|
2738
|
+
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.",
|
|
2368
2739
|
inputSchema: {
|
|
2369
2740
|
type: "object",
|
|
2370
2741
|
properties: {
|
|
2742
|
+
lease: {
|
|
2743
|
+
type: "string",
|
|
2744
|
+
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."
|
|
2745
|
+
},
|
|
2371
2746
|
target: {
|
|
2372
2747
|
type: "string",
|
|
2373
2748
|
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)."
|
|
@@ -2387,6 +2762,10 @@ var MANIFEST = [
|
|
|
2387
2762
|
inputSchema: {
|
|
2388
2763
|
type: "object",
|
|
2389
2764
|
properties: {
|
|
2765
|
+
lease: {
|
|
2766
|
+
type: "string",
|
|
2767
|
+
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."
|
|
2768
|
+
},
|
|
2390
2769
|
target: {
|
|
2391
2770
|
type: "string",
|
|
2392
2771
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2423,6 +2802,10 @@ var MANIFEST = [
|
|
|
2423
2802
|
inputSchema: {
|
|
2424
2803
|
type: "object",
|
|
2425
2804
|
properties: {
|
|
2805
|
+
lease: {
|
|
2806
|
+
type: "string",
|
|
2807
|
+
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."
|
|
2808
|
+
},
|
|
2426
2809
|
target: {
|
|
2427
2810
|
type: "string",
|
|
2428
2811
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2446,6 +2829,10 @@ var MANIFEST = [
|
|
|
2446
2829
|
inputSchema: {
|
|
2447
2830
|
type: "object",
|
|
2448
2831
|
properties: {
|
|
2832
|
+
lease: {
|
|
2833
|
+
type: "string",
|
|
2834
|
+
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."
|
|
2835
|
+
},
|
|
2449
2836
|
target: {
|
|
2450
2837
|
type: "string",
|
|
2451
2838
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2494,6 +2881,10 @@ var MANIFEST = [
|
|
|
2494
2881
|
inputSchema: {
|
|
2495
2882
|
type: "object",
|
|
2496
2883
|
properties: {
|
|
2884
|
+
lease: {
|
|
2885
|
+
type: "string",
|
|
2886
|
+
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."
|
|
2887
|
+
},
|
|
2497
2888
|
target: {
|
|
2498
2889
|
type: "string",
|
|
2499
2890
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2523,6 +2914,10 @@ var MANIFEST = [
|
|
|
2523
2914
|
inputSchema: {
|
|
2524
2915
|
type: "object",
|
|
2525
2916
|
properties: {
|
|
2917
|
+
lease: {
|
|
2918
|
+
type: "string",
|
|
2919
|
+
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."
|
|
2920
|
+
},
|
|
2526
2921
|
target: {
|
|
2527
2922
|
type: "string",
|
|
2528
2923
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2565,6 +2960,10 @@ var MANIFEST = [
|
|
|
2565
2960
|
inputSchema: {
|
|
2566
2961
|
type: "object",
|
|
2567
2962
|
properties: {
|
|
2963
|
+
lease: {
|
|
2964
|
+
type: "string",
|
|
2965
|
+
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."
|
|
2966
|
+
},
|
|
2568
2967
|
target: {
|
|
2569
2968
|
type: "string",
|
|
2570
2969
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2594,6 +2993,10 @@ var MANIFEST = [
|
|
|
2594
2993
|
inputSchema: {
|
|
2595
2994
|
type: "object",
|
|
2596
2995
|
properties: {
|
|
2996
|
+
lease: {
|
|
2997
|
+
type: "string",
|
|
2998
|
+
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."
|
|
2999
|
+
},
|
|
2597
3000
|
target: {
|
|
2598
3001
|
type: "string",
|
|
2599
3002
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2622,6 +3025,10 @@ var MANIFEST = [
|
|
|
2622
3025
|
inputSchema: {
|
|
2623
3026
|
type: "object",
|
|
2624
3027
|
properties: {
|
|
3028
|
+
lease: {
|
|
3029
|
+
type: "string",
|
|
3030
|
+
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."
|
|
3031
|
+
},
|
|
2625
3032
|
target: {
|
|
2626
3033
|
type: "string",
|
|
2627
3034
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2653,10 +3060,14 @@ var MANIFEST = [
|
|
|
2653
3060
|
},
|
|
2654
3061
|
{
|
|
2655
3062
|
name: "take_screenshot",
|
|
2656
|
-
description: "Capture the viewport (default), the full scrollable page (fullPage), or a single element (uid or selector
|
|
3063
|
+
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.",
|
|
2657
3064
|
inputSchema: {
|
|
2658
3065
|
type: "object",
|
|
2659
3066
|
properties: {
|
|
3067
|
+
lease: {
|
|
3068
|
+
type: "string",
|
|
3069
|
+
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."
|
|
3070
|
+
},
|
|
2660
3071
|
target: {
|
|
2661
3072
|
type: "string",
|
|
2662
3073
|
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."
|
|
@@ -2679,7 +3090,7 @@ var MANIFEST = [
|
|
|
2679
3090
|
},
|
|
2680
3091
|
uid: {
|
|
2681
3092
|
type: "number",
|
|
2682
|
-
description: "Element to clip to
|
|
3093
|
+
description: "Element to clip to: a CDP backendDOMNodeId obtained from take_snapshot. Mutually exclusive with selector."
|
|
2683
3094
|
},
|
|
2684
3095
|
selector: {
|
|
2685
3096
|
type: "string",
|
|
@@ -2704,6 +3115,10 @@ var MANIFEST = [
|
|
|
2704
3115
|
inputSchema: {
|
|
2705
3116
|
type: "object",
|
|
2706
3117
|
properties: {
|
|
3118
|
+
lease: {
|
|
3119
|
+
type: "string",
|
|
3120
|
+
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."
|
|
3121
|
+
},
|
|
2707
3122
|
target: {
|
|
2708
3123
|
type: "string",
|
|
2709
3124
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2800,6 +3215,10 @@ var MANIFEST = [
|
|
|
2800
3215
|
inputSchema: {
|
|
2801
3216
|
type: "object",
|
|
2802
3217
|
properties: {
|
|
3218
|
+
lease: {
|
|
3219
|
+
type: "string",
|
|
3220
|
+
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."
|
|
3221
|
+
},
|
|
2803
3222
|
target: {
|
|
2804
3223
|
type: "string",
|
|
2805
3224
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2834,6 +3253,10 @@ var MANIFEST = [
|
|
|
2834
3253
|
inputSchema: {
|
|
2835
3254
|
type: "object",
|
|
2836
3255
|
properties: {
|
|
3256
|
+
lease: {
|
|
3257
|
+
type: "string",
|
|
3258
|
+
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."
|
|
3259
|
+
},
|
|
2837
3260
|
target: {
|
|
2838
3261
|
type: "string",
|
|
2839
3262
|
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>."
|
|
@@ -2867,6 +3290,10 @@ var MANIFEST = [
|
|
|
2867
3290
|
inputSchema: {
|
|
2868
3291
|
type: "object",
|
|
2869
3292
|
properties: {
|
|
3293
|
+
lease: {
|
|
3294
|
+
type: "string",
|
|
3295
|
+
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."
|
|
3296
|
+
},
|
|
2870
3297
|
target: {
|
|
2871
3298
|
type: "string",
|
|
2872
3299
|
description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
|
|
@@ -2886,10 +3313,14 @@ var MANIFEST = [
|
|
|
2886
3313
|
},
|
|
2887
3314
|
{
|
|
2888
3315
|
name: "get_console_message",
|
|
2889
|
-
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
|
|
3316
|
+
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.",
|
|
2890
3317
|
inputSchema: {
|
|
2891
3318
|
type: "object",
|
|
2892
3319
|
properties: {
|
|
3320
|
+
lease: {
|
|
3321
|
+
type: "string",
|
|
3322
|
+
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."
|
|
3323
|
+
},
|
|
2893
3324
|
target: {
|
|
2894
3325
|
type: "string",
|
|
2895
3326
|
description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
|
|
@@ -2909,6 +3340,10 @@ var MANIFEST = [
|
|
|
2909
3340
|
inputSchema: {
|
|
2910
3341
|
type: "object",
|
|
2911
3342
|
properties: {
|
|
3343
|
+
lease: {
|
|
3344
|
+
type: "string",
|
|
3345
|
+
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."
|
|
3346
|
+
},
|
|
2912
3347
|
target: {
|
|
2913
3348
|
type: "string",
|
|
2914
3349
|
description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
|
|
@@ -2932,17 +3367,21 @@ var MANIFEST = [
|
|
|
2932
3367
|
},
|
|
2933
3368
|
{
|
|
2934
3369
|
name: "get_network_request",
|
|
2935
|
-
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
|
|
3370
|
+
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).",
|
|
2936
3371
|
inputSchema: {
|
|
2937
3372
|
type: "object",
|
|
2938
3373
|
properties: {
|
|
3374
|
+
lease: {
|
|
3375
|
+
type: "string",
|
|
3376
|
+
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."
|
|
3377
|
+
},
|
|
2939
3378
|
target: {
|
|
2940
3379
|
type: "string",
|
|
2941
3380
|
description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
|
|
2942
3381
|
},
|
|
2943
3382
|
requestId: {
|
|
2944
3383
|
type: "string",
|
|
2945
|
-
description: "Match by exact requestId (metadata only
|
|
3384
|
+
description: "Match by exact requestId (metadata only, cannot fetch a body, since reload re-mints requestIds)."
|
|
2946
3385
|
},
|
|
2947
3386
|
url: {
|
|
2948
3387
|
type: "string",
|
|
@@ -2967,6 +3406,10 @@ var MANIFEST = [
|
|
|
2967
3406
|
inputSchema: {
|
|
2968
3407
|
type: "object",
|
|
2969
3408
|
properties: {
|
|
3409
|
+
lease: {
|
|
3410
|
+
type: "string",
|
|
3411
|
+
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."
|
|
3412
|
+
},
|
|
2970
3413
|
target: {
|
|
2971
3414
|
type: "string",
|
|
2972
3415
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -2985,10 +3428,14 @@ var MANIFEST = [
|
|
|
2985
3428
|
},
|
|
2986
3429
|
{
|
|
2987
3430
|
name: "performance_stop_trace",
|
|
2988
|
-
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)
|
|
3431
|
+
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.",
|
|
2989
3432
|
inputSchema: {
|
|
2990
3433
|
type: "object",
|
|
2991
3434
|
properties: {
|
|
3435
|
+
lease: {
|
|
3436
|
+
type: "string",
|
|
3437
|
+
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."
|
|
3438
|
+
},
|
|
2992
3439
|
target: {
|
|
2993
3440
|
type: "string",
|
|
2994
3441
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -3000,10 +3447,14 @@ var MANIFEST = [
|
|
|
3000
3447
|
},
|
|
3001
3448
|
{
|
|
3002
3449
|
name: "performance_analyze_insight",
|
|
3003
|
-
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
|
|
3450
|
+
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).",
|
|
3004
3451
|
inputSchema: {
|
|
3005
3452
|
type: "object",
|
|
3006
3453
|
properties: {
|
|
3454
|
+
lease: {
|
|
3455
|
+
type: "string",
|
|
3456
|
+
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."
|
|
3457
|
+
},
|
|
3007
3458
|
tracePath: {
|
|
3008
3459
|
type: "string",
|
|
3009
3460
|
description: "Filesystem path to a trace JSON file written by performance_trace or performance_stop_trace (bare array OR {traceEvents:[...]}). Required."
|
|
@@ -3015,10 +3466,14 @@ var MANIFEST = [
|
|
|
3015
3466
|
},
|
|
3016
3467
|
{
|
|
3017
3468
|
name: "performance_trace",
|
|
3018
|
-
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
|
|
3469
|
+
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.",
|
|
3019
3470
|
inputSchema: {
|
|
3020
3471
|
type: "object",
|
|
3021
3472
|
properties: {
|
|
3473
|
+
lease: {
|
|
3474
|
+
type: "string",
|
|
3475
|
+
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."
|
|
3476
|
+
},
|
|
3022
3477
|
target: {
|
|
3023
3478
|
type: "string",
|
|
3024
3479
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -3053,6 +3508,10 @@ var MANIFEST = [
|
|
|
3053
3508
|
inputSchema: {
|
|
3054
3509
|
type: "object",
|
|
3055
3510
|
properties: {
|
|
3511
|
+
lease: {
|
|
3512
|
+
type: "string",
|
|
3513
|
+
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."
|
|
3514
|
+
},
|
|
3056
3515
|
target: {
|
|
3057
3516
|
type: "string",
|
|
3058
3517
|
description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
|
|
@@ -3072,9 +3531,13 @@ var MANIFEST = [
|
|
|
3072
3531
|
inputSchema: {
|
|
3073
3532
|
type: "object",
|
|
3074
3533
|
properties: {
|
|
3534
|
+
lease: {
|
|
3535
|
+
type: "string",
|
|
3536
|
+
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."
|
|
3537
|
+
},
|
|
3075
3538
|
url: {
|
|
3076
3539
|
type: "string",
|
|
3077
|
-
description: "The URL to audit. Required
|
|
3540
|
+
description: "The URL to audit. Required, and never points at a user tab implicitly."
|
|
3078
3541
|
},
|
|
3079
3542
|
categories: {
|
|
3080
3543
|
type: "array",
|
|
@@ -3108,10 +3571,14 @@ var MANIFEST = [
|
|
|
3108
3571
|
},
|
|
3109
3572
|
{
|
|
3110
3573
|
name: "mock_request",
|
|
3111
|
-
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
|
|
3574
|
+
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).",
|
|
3112
3575
|
inputSchema: {
|
|
3113
3576
|
type: "object",
|
|
3114
3577
|
properties: {
|
|
3578
|
+
lease: {
|
|
3579
|
+
type: "string",
|
|
3580
|
+
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."
|
|
3581
|
+
},
|
|
3115
3582
|
target: {
|
|
3116
3583
|
type: "string",
|
|
3117
3584
|
description: "Target selector: active | index:N | url:<substr> | title:<substr> | <targetId>. Defaults to the active page."
|
|
@@ -3178,6 +3645,10 @@ var MANIFEST = [
|
|
|
3178
3645
|
inputSchema: {
|
|
3179
3646
|
type: "object",
|
|
3180
3647
|
properties: {
|
|
3648
|
+
lease: {
|
|
3649
|
+
type: "string",
|
|
3650
|
+
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."
|
|
3651
|
+
},
|
|
3181
3652
|
target: {
|
|
3182
3653
|
type: "string",
|
|
3183
3654
|
description: "Unused filter placeholder; list_mocks returns all active sessions."
|
|
@@ -3193,6 +3664,10 @@ var MANIFEST = [
|
|
|
3193
3664
|
inputSchema: {
|
|
3194
3665
|
type: "object",
|
|
3195
3666
|
properties: {
|
|
3667
|
+
lease: {
|
|
3668
|
+
type: "string",
|
|
3669
|
+
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."
|
|
3670
|
+
},
|
|
3196
3671
|
target: {
|
|
3197
3672
|
type: "string",
|
|
3198
3673
|
description: "Target selector whose mock session to clear. Defaults to the active page. Ignored when all:true."
|
|
@@ -3205,6 +3680,60 @@ var MANIFEST = [
|
|
|
3205
3680
|
required: [],
|
|
3206
3681
|
additionalProperties: false
|
|
3207
3682
|
}
|
|
3683
|
+
},
|
|
3684
|
+
{
|
|
3685
|
+
name: "claim_page",
|
|
3686
|
+
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.",
|
|
3687
|
+
inputSchema: {
|
|
3688
|
+
type: "object",
|
|
3689
|
+
properties: {
|
|
3690
|
+
targetId: {
|
|
3691
|
+
type: "string",
|
|
3692
|
+
description: "Claim this already-open page target instead of opening a new tab. Must be an exact target id, not a selector."
|
|
3693
|
+
},
|
|
3694
|
+
url: {
|
|
3695
|
+
type: "string",
|
|
3696
|
+
description: "When opening a fresh tab, navigate it here. Ignored when targetId is given."
|
|
3697
|
+
},
|
|
3698
|
+
label: {
|
|
3699
|
+
type: "string",
|
|
3700
|
+
description: "Agent label recorded on the lease and surfaced in conflict errors and list_leases. Defaults to pid-<pid>."
|
|
3701
|
+
},
|
|
3702
|
+
ttlMs: {
|
|
3703
|
+
type: "number",
|
|
3704
|
+
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."
|
|
3705
|
+
}
|
|
3706
|
+
},
|
|
3707
|
+
required: [],
|
|
3708
|
+
additionalProperties: false
|
|
3709
|
+
}
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
name: "release_page",
|
|
3713
|
+
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.",
|
|
3714
|
+
inputSchema: {
|
|
3715
|
+
type: "object",
|
|
3716
|
+
properties: {
|
|
3717
|
+
lease: {
|
|
3718
|
+
type: "string",
|
|
3719
|
+
description: "The opaque token claim_page (or new_page with claim:true) returned."
|
|
3720
|
+
}
|
|
3721
|
+
},
|
|
3722
|
+
required: [
|
|
3723
|
+
"lease"
|
|
3724
|
+
],
|
|
3725
|
+
additionalProperties: false
|
|
3726
|
+
}
|
|
3727
|
+
},
|
|
3728
|
+
{
|
|
3729
|
+
name: "list_leases",
|
|
3730
|
+
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.',
|
|
3731
|
+
inputSchema: {
|
|
3732
|
+
type: "object",
|
|
3733
|
+
properties: {},
|
|
3734
|
+
required: [],
|
|
3735
|
+
additionalProperties: false
|
|
3736
|
+
}
|
|
3208
3737
|
}
|
|
3209
3738
|
];
|
|
3210
3739
|
|
|
@@ -3963,12 +4492,17 @@ async function safeTitle(conn, contextId) {
|
|
|
3963
4492
|
async function pageInfoOf(conn, ctx) {
|
|
3964
4493
|
return { id: ctx.context, url: ctx.url, title: await safeTitle(conn, ctx.context) };
|
|
3965
4494
|
}
|
|
3966
|
-
async function resolveContext(conn, selector) {
|
|
4495
|
+
async function resolveContext(conn, selector, opts = {}) {
|
|
3967
4496
|
const { contexts } = await conn.send("browsingContext.getTree", {}).catch((e) => {
|
|
3968
4497
|
throw mapBidiError(e);
|
|
3969
4498
|
});
|
|
3970
4499
|
if (!contexts.length)
|
|
3971
4500
|
throw driverError2("no-such-target", "no browsing contexts available");
|
|
4501
|
+
const hit = await pickContext(conn, contexts, selector);
|
|
4502
|
+
await assertLeaseOk("firefox", hit.context, { lease: opts.lease, url: hit.url });
|
|
4503
|
+
return hit;
|
|
4504
|
+
}
|
|
4505
|
+
async function pickContext(conn, contexts, selector) {
|
|
3972
4506
|
if (selector === undefined || selector === "active")
|
|
3973
4507
|
return contexts[0];
|
|
3974
4508
|
if (selector.startsWith("index:")) {
|
|
@@ -4489,7 +5023,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
4489
5023
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
4490
5024
|
import { createConnection, createServer } from "node:net";
|
|
4491
5025
|
import { tmpdir } from "node:os";
|
|
4492
|
-
import { join as
|
|
5026
|
+
import { join as join5 } from "node:path";
|
|
4493
5027
|
function resolveBinary() {
|
|
4494
5028
|
const candidates = [
|
|
4495
5029
|
process.env.FIREFOX_BIN,
|
|
@@ -4563,7 +5097,7 @@ async function launchFirefox(opts = {}) {
|
|
|
4563
5097
|
const binary = resolveBinary();
|
|
4564
5098
|
const port = await pickPort(opts.port);
|
|
4565
5099
|
const isThrowaway = opts.profilePath === undefined;
|
|
4566
|
-
const profilePath = opts.profilePath ?? mkdtempSync(
|
|
5100
|
+
const profilePath = opts.profilePath ?? mkdtempSync(join5(tmpdir(), "cdp-toolkit-ff-profile-"));
|
|
4567
5101
|
const args = ["--profile", profilePath, "--remote-debugging-port", String(port), "--no-remote"];
|
|
4568
5102
|
if (opts.headless)
|
|
4569
5103
|
args.push("--headless");
|
|
@@ -4828,11 +5362,12 @@ var BIDI_ONLY_TOOLS = {
|
|
|
4828
5362
|
// src/firefox-tools.ts
|
|
4829
5363
|
var FIREFOX_TOOLS = {
|
|
4830
5364
|
...SHARED_TOOLS,
|
|
4831
|
-
...BIDI_ONLY_TOOLS
|
|
5365
|
+
...BIDI_ONLY_TOOLS,
|
|
5366
|
+
...LEASE_TOOLS
|
|
4832
5367
|
};
|
|
4833
5368
|
|
|
4834
5369
|
// src/mcp.ts
|
|
4835
|
-
var VERSION = "1.
|
|
5370
|
+
var VERSION = "1.2.0";
|
|
4836
5371
|
var BROWSER = resolveBrowserKind(stripBrowserFlag(process.argv.slice(2)));
|
|
4837
5372
|
var AVAILABILITY = toolAvailability(BROWSER);
|
|
4838
5373
|
var AVAILABLE_NAMES = new Set(AVAILABILITY.available);
|
|
@@ -4862,29 +5397,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4862
5397
|
isError: true
|
|
4863
5398
|
};
|
|
4864
5399
|
}
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
5400
|
+
return withLeaseScope(leaseFromArgs(args), async () => {
|
|
5401
|
+
if (BROWSER === "chrome") {
|
|
5402
|
+
const fn = dispatch[name];
|
|
5403
|
+
if (!fn)
|
|
5404
|
+
return { content: [{ type: "text", text: `unknown tool: ${name}` }], isError: true };
|
|
5405
|
+
try {
|
|
5406
|
+
const result = await fn(args);
|
|
5407
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
5408
|
+
} catch (err) {
|
|
5409
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5410
|
+
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
const neutralFn = neutralDispatch[name];
|
|
5414
|
+
if (!neutralFn)
|
|
4868
5415
|
return { content: [{ type: "text", text: `unknown tool: ${name}` }], isError: true };
|
|
4869
5416
|
try {
|
|
4870
|
-
const
|
|
5417
|
+
const session = await getOrCreateFirefoxSession();
|
|
5418
|
+
const result = await neutralFn(session.driver, args);
|
|
4871
5419
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
4872
5420
|
} catch (err) {
|
|
4873
5421
|
const message = err instanceof Error ? err.message : String(err);
|
|
4874
5422
|
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
4875
5423
|
}
|
|
4876
|
-
}
|
|
4877
|
-
const neutralFn = neutralDispatch[name];
|
|
4878
|
-
if (!neutralFn)
|
|
4879
|
-
return { content: [{ type: "text", text: `unknown tool: ${name}` }], isError: true };
|
|
4880
|
-
try {
|
|
4881
|
-
const session = await getOrCreateFirefoxSession();
|
|
4882
|
-
const result = await neutralFn(session.driver, args);
|
|
4883
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
4884
|
-
} catch (err) {
|
|
4885
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4886
|
-
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
4887
|
-
}
|
|
5424
|
+
});
|
|
4888
5425
|
});
|
|
4889
5426
|
auditCoverage();
|
|
4890
5427
|
await server.connect(new StdioServerTransport);
|