cdp-toolkit 2.2.0 → 2.5.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 +33 -0
- package/README.md +43 -17
- package/dist/bidi/mux-daemon.js +2 -0
- package/dist/cli.js +695 -42
- package/dist/index.js +514 -0
- package/dist/mcp.js +724 -30
- package/package.json +5 -3
- package/src/cdp/driver.ts +1 -1
- package/src/driver.ts +3 -0
- package/src/firefox-tools.ts +6 -3
- package/src/index.ts +16 -3
- package/src/manifest.ts +138 -0
- package/src/toolDocs.ts +37 -0
- package/src/toolGroups.ts +3 -2
- package/src/tools/extract.ts +777 -0
- package/src/tools/focus-emulation.ts +185 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-toolkit",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "A lightweight chrome-devtools-mcp alternative: an MCP server + CLI that drives the tabs you name, one target per call over one direct WebSocket, with a bounded timeout on every call, so a stuck page can't wedge your agent or force a /mcp restart. Chrome over CDP and Firefox over WebDriver BiDi, with tools a backend can't run absent from tools/list rather than throwing mid-task.
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"description": "A lightweight chrome-devtools-mcp alternative: an MCP server + CLI that drives the tabs you name, one target per call over one direct WebSocket, with a bounded timeout on every call, so a stuck page can't wedge your agent or force a /mcp restart. Chrome over CDP and Firefox over WebDriver BiDi, with tools a backend can't run absent from tools/list rather than throwing mid-task. 48 tools including built-in network mocking, a cookie group, tab leases with human-activity detection, real HTML5 drag-and-drop, raw mouse/scroll dispatch, downloads, permission grants, and tab-to-video screen recording so several agents can drive one browser without stealing each other's tabs or a human's. No Puppeteer, no all-target fan-out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": {
|
|
@@ -84,7 +84,9 @@
|
|
|
84
84
|
"lease:smoke": "bun run ./test/lease-smoke.ts",
|
|
85
85
|
"staleness:smoke": "bun run ./test/staleness-smoke.ts",
|
|
86
86
|
"input:smoke": "bun run ./test/input-smoke.ts",
|
|
87
|
-
"extension:smoke": "bun run ./test/extension-smoke.ts"
|
|
87
|
+
"extension:smoke": "bun run ./test/extension-smoke.ts",
|
|
88
|
+
"focus:smoke": "bun run ./test/focus-emulation-smoke.ts",
|
|
89
|
+
"extract:smoke": "bun run ./test/extract-smoke.ts"
|
|
88
90
|
},
|
|
89
91
|
"devDependencies": {
|
|
90
92
|
"@modelcontextprotocol/client": "^2.0.0",
|
package/src/cdp/driver.ts
CHANGED
|
@@ -651,7 +651,7 @@ const CDP_CAPABILITIES: ReadonlySet<Capability> = new Set<Capability>([
|
|
|
651
651
|
"emulate.mediaFeatures", "emulate.deviceMetrics", "emulate.networkConditions",
|
|
652
652
|
"screenshot.fullPage", "screenshot.element", "screenshot.scale", "screenshot.renderSize", "screenshot.tile", "network.intercept",
|
|
653
653
|
"snapshot.accessibilityTree", "input.insertTextAtomic", "locate.text", "locate.xpath",
|
|
654
|
-
"capture.screencast", "input.raw", "input.html5Drag",
|
|
654
|
+
"capture.screencast", "input.raw", "input.html5Drag", "emulate.focus",
|
|
655
655
|
"browser.downloads", "browser.permissions", "worker.targets",
|
|
656
656
|
]);
|
|
657
657
|
/* ---------------------------------- PageDriver ---------------------------------- */
|
package/src/driver.ts
CHANGED
|
@@ -150,6 +150,7 @@ export type Capability =
|
|
|
150
150
|
| "locate.xpath" // find a node by xpath
|
|
151
151
|
| "input.raw" // raw single mouse-event dispatch (dispatch_mouse's move/down/up primitive); Chrome only
|
|
152
152
|
| "input.html5Drag" // real HTML5 drag events (drag's mode:"html5"); Chrome only, see PageDriver.drag
|
|
153
|
+
| "emulate.focus" // fake page focus without moving the OS window (focus_emulation / click_focus_gated); Chrome only — BiDi's emulation module has no focus primitive
|
|
153
154
|
| "browser.downloads" // capture a file download to a known path (wait_for_download); Chrome only
|
|
154
155
|
| "browser.permissions" // grant/reset browser permissions for an origin (grant_permissions); Chrome only
|
|
155
156
|
| "worker.targets"; // address a service/shared worker as a target ("worker:<substr>"), incl. waking an evicted MV3 worker; Chrome only
|
|
@@ -172,6 +173,8 @@ export const REQUIRED_CAPABILITIES: Partial<Record<ToolName, readonly Capability
|
|
|
172
173
|
list_mocks: ["network.intercept"],
|
|
173
174
|
clear_mocks: ["network.intercept"],
|
|
174
175
|
dispatch_mouse: ["input.raw"],
|
|
176
|
+
focus_emulation: ["emulate.focus"],
|
|
177
|
+
click_focus_gated: ["emulate.focus"],
|
|
175
178
|
wait_for_download: ["browser.downloads"],
|
|
176
179
|
grant_permissions: ["browser.permissions"],
|
|
177
180
|
} as const;
|
package/src/firefox-tools.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The complete Firefox tool registry: the 23 tools shared with Chrome (src/shared-tools.ts), the
|
|
3
|
-
* 7 Firefox-only console/network/mock tools (src/bidi-tools.ts),
|
|
4
|
-
* (src/leases-tools.ts), which
|
|
5
|
-
*
|
|
3
|
+
* 7 Firefox-only console/network/mock tools (src/bidi-tools.ts), the 3 lease tools
|
|
4
|
+
* (src/leases-tools.ts), and extract_page (src/tools/extract.ts), which is backend-neutral by
|
|
5
|
+
* construction (its page work runs entirely through Driver.evaluate). This is what src/neutral.ts
|
|
6
|
+
* used to be; it now composes four files instead of reimplementing everything in one.
|
|
6
7
|
*/
|
|
7
8
|
import type { BrowserDriver } from "./driver.ts";
|
|
8
9
|
import type { ToolName } from "./index.ts";
|
|
9
10
|
import { SHARED_TOOLS } from "./shared-tools.ts";
|
|
10
11
|
import { BIDI_ONLY_TOOLS } from "./bidi-tools.ts";
|
|
11
12
|
import { LEASE_TOOLS } from "./leases-tools.ts";
|
|
13
|
+
import { extractPage } from "./tools/extract.ts";
|
|
12
14
|
|
|
13
15
|
export const FIREFOX_TOOLS: Partial<Record<ToolName, (driver: BrowserDriver, args: never) => Promise<unknown>>> = {
|
|
14
16
|
...SHARED_TOOLS,
|
|
15
17
|
...BIDI_ONLY_TOOLS,
|
|
16
18
|
...LEASE_TOOLS,
|
|
19
|
+
extract_page: extractPage,
|
|
17
20
|
};
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Two exports:
|
|
5
5
|
* 1. `TOOLS` : the registry mapping every chrome-devtools-mcp tool name
|
|
6
6
|
* (snake_case string, all 29 of them) plus the toolkit's own superset
|
|
7
|
-
* tools,
|
|
7
|
+
* tools, 48 entries in all, to its raw-CDP implementation function. The
|
|
8
8
|
* CLI and any embedder dispatch through this single table.
|
|
9
9
|
* 2. The client primitives, re-exported so consumers can build their own
|
|
10
10
|
* flows on the same connection/timeout machinery the tools use.
|
|
@@ -43,9 +43,13 @@ import { lighthouseAudit } from "./tools/lighthouse.ts";
|
|
|
43
43
|
import { mockRequest, listMocks, clearMocks } from "./tools/network_mock.ts";
|
|
44
44
|
// --- raw mouse dispatch (Input.dispatchMouseEvent, one event per call) : chrome-only, capability "input.raw" ---
|
|
45
45
|
import { dispatchMouse } from "./tools/dispatch-mouse.ts";
|
|
46
|
+
// --- focus emulation + focus-gated click (Emulation.setFocusEmulationEnabled) : chrome-only, capability "emulate.focus" ---
|
|
47
|
+
import { focusEmulation, clickFocusGated } from "./tools/focus-emulation.ts";
|
|
46
48
|
// --- downloads + permissions (browser-endpoint state on a standing connection) : chrome-only, 1.8.0 Track P3 ---
|
|
47
49
|
import { waitForDownload } from "./tools/downloads.ts";
|
|
48
50
|
import { grantPermissions } from "./tools/permissions.ts";
|
|
51
|
+
// --- structured extraction (cleaned page HTML -> OpenAI-compatible endpoint, both backends) ---
|
|
52
|
+
import { extractPage } from "./tools/extract.ts";
|
|
49
53
|
|
|
50
54
|
/** A toolkit tool: a single typed-args function returning JSON-serializable data. */
|
|
51
55
|
export type ToolFn = (args: never) => Promise<unknown>;
|
|
@@ -81,8 +85,8 @@ function onCdp<A>(fn: (driver: ReturnType<typeof createCdpDriver>, args: A) => P
|
|
|
81
85
|
* named path, and `grant_permissions`, which answers permission prompts for an origin; both
|
|
82
86
|
* chrome-only and both driven from a standing browser-endpoint connection, see
|
|
83
87
|
* tools/browser-session.ts).
|
|
84
|
-
* 29 + 1 + 3 + 3 + 3 + 2 + 2 + 2 =
|
|
85
|
-
*
|
|
88
|
+
* 29 + 1 + 3 + 3 + 3 + 2 + 2 + 2 + 1 (extract_page) + 2 (focus pair) = 48 entries total. Listed explicitly so the mapping is auditable at a glance and
|
|
89
|
+
* the CLI can `--list` it.
|
|
86
90
|
*
|
|
87
91
|
* 20 of the 29 MCP-parity tools (pages/navigation/evaluate/snapshot/interaction/
|
|
88
92
|
* screenshot+emulation/dialogs) now route through shared-tools.ts's single Driver-based
|
|
@@ -145,12 +149,21 @@ export const TOOLS = {
|
|
|
145
149
|
clear_mocks: clearMocks,
|
|
146
150
|
// raw mouse dispatch (1) : 1.8.0 Track P1 toolkit addition, chrome-only (capability "input.raw")
|
|
147
151
|
dispatch_mouse: dispatchMouse,
|
|
152
|
+
// focus emulation (2) : fake page focus without stealing OS focus; trusted click on a focus-gated
|
|
153
|
+
// button (e.g. Claude Code OAuth Authorize). chrome-only (capability "emulate.focus")
|
|
154
|
+
focus_emulation: focusEmulation,
|
|
155
|
+
click_focus_gated: clickFocusGated,
|
|
148
156
|
// downloads + permissions (2) : 1.8.0 Track P3 toolkit additions, chrome-only (capabilities
|
|
149
157
|
// "browser.downloads" / "browser.permissions"). Both drive browser-endpoint state that Chrome
|
|
150
158
|
// reverts when the issuing client disconnects, so both run on tools/browser-session.ts's standing
|
|
151
159
|
// connection rather than a per-call socket : see that module's header for the measurements.
|
|
152
160
|
wait_for_download: waitForDownload,
|
|
153
161
|
grant_permissions: grantPermissions,
|
|
162
|
+
// structured extraction (1) : toolkit addition, both backends — a page's cleaned HTML
|
|
163
|
+
// POSTed to an OpenAI-compatible /chat/completions endpoint (default: loopback
|
|
164
|
+
// llm-ferry) with the caller's JSON Schema as a strict response_format, returning
|
|
165
|
+
// schema-conformant JSON + token usage. API key is env-only (CDP_EXTRACT_API_KEY).
|
|
166
|
+
extract_page: onCdp(extractPage),
|
|
154
167
|
// tab leases (3) : opt-in ownership so many agents can share one browser
|
|
155
168
|
claim_page: onCdp(LEASE_TOOLS.claim_page),
|
|
156
169
|
release_page: onCdp(LEASE_TOOLS.release_page),
|
package/src/manifest.ts
CHANGED
|
@@ -674,6 +674,70 @@ export const MANIFEST: ToolSpec[] = [
|
|
|
674
674
|
"additionalProperties": false
|
|
675
675
|
}
|
|
676
676
|
},
|
|
677
|
+
{
|
|
678
|
+
"name": "focus_emulation",
|
|
679
|
+
"description": "Toggle page focus emulation: the page reports focused/visible without the OS window moving; Chrome-only.",
|
|
680
|
+
"inputSchema": {
|
|
681
|
+
"type": "object",
|
|
682
|
+
"properties": {
|
|
683
|
+
"lease": {
|
|
684
|
+
"type": "string",
|
|
685
|
+
"description": "Lease token; omit for a tab this process already holds. See server instructions."
|
|
686
|
+
},
|
|
687
|
+
"target": {
|
|
688
|
+
"type": "string",
|
|
689
|
+
"description": "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
|
|
690
|
+
},
|
|
691
|
+
"enabled": {
|
|
692
|
+
"type": "boolean",
|
|
693
|
+
"description": "true = page reports focused; false = restore real focus state."
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
"required": [
|
|
697
|
+
"enabled"
|
|
698
|
+
],
|
|
699
|
+
"additionalProperties": false
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"name": "click_focus_gated",
|
|
704
|
+
"description": "Emulate page focus, wait for a focus-gated button to enable, trusted-click it, restore; Chrome-only.",
|
|
705
|
+
"inputSchema": {
|
|
706
|
+
"type": "object",
|
|
707
|
+
"properties": {
|
|
708
|
+
"lease": {
|
|
709
|
+
"type": "string",
|
|
710
|
+
"description": "Lease token; omit for a tab this process already holds. See server instructions."
|
|
711
|
+
},
|
|
712
|
+
"target": {
|
|
713
|
+
"type": "string",
|
|
714
|
+
"description": "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
|
|
715
|
+
},
|
|
716
|
+
"selector": {
|
|
717
|
+
"type": "string",
|
|
718
|
+
"description": "CSS selector of the button; exactly one of selector or text."
|
|
719
|
+
},
|
|
720
|
+
"text": {
|
|
721
|
+
"type": "string",
|
|
722
|
+
"description": "Visible-text match for the button (e.g. \"Authorize\"); exactly one of selector or text."
|
|
723
|
+
},
|
|
724
|
+
"timeoutMs": {
|
|
725
|
+
"type": "number",
|
|
726
|
+
"description": "Max ms to poll for the button to become enabled. Default 30000."
|
|
727
|
+
},
|
|
728
|
+
"pollMs": {
|
|
729
|
+
"type": "number",
|
|
730
|
+
"description": "Poll interval ms. Default 500."
|
|
731
|
+
},
|
|
732
|
+
"keepFocus": {
|
|
733
|
+
"type": "boolean",
|
|
734
|
+
"description": "Leave focus emulation on after the click (default false = restored)."
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"required": [],
|
|
738
|
+
"additionalProperties": false
|
|
739
|
+
}
|
|
740
|
+
},
|
|
677
741
|
{
|
|
678
742
|
"name": "wait_for_download",
|
|
679
743
|
"description": "Arm or collect a browser download; arm capture before triggering the download.",
|
|
@@ -1699,5 +1763,79 @@ export const MANIFEST: ToolSpec[] = [
|
|
|
1699
1763
|
"required": [],
|
|
1700
1764
|
"additionalProperties": false
|
|
1701
1765
|
}
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
"name": "extract_page",
|
|
1769
|
+
"description": "Extract schema-conformant JSON from the target page's cleaned HTML via an OpenAI-compatible endpoint; default endpoint is local loopback.",
|
|
1770
|
+
"inputSchema": {
|
|
1771
|
+
"type": "object",
|
|
1772
|
+
"properties": {
|
|
1773
|
+
"lease": {
|
|
1774
|
+
"type": "string",
|
|
1775
|
+
"description": "Lease token; omit for a tab this process already holds. See server instructions."
|
|
1776
|
+
},
|
|
1777
|
+
"target": {
|
|
1778
|
+
"type": "string",
|
|
1779
|
+
"description": "Page selector (index:/url:/title:/label:/targetId, default active); grammar in server instructions."
|
|
1780
|
+
},
|
|
1781
|
+
"schema": {
|
|
1782
|
+
"type": "object",
|
|
1783
|
+
"description": "JSON Schema (type object) the extracted JSON must conform to; extraction instructions live in its property descriptions."
|
|
1784
|
+
},
|
|
1785
|
+
"selector": {
|
|
1786
|
+
"type": "string",
|
|
1787
|
+
"description": "CSS selector scoping extraction to a subtree."
|
|
1788
|
+
},
|
|
1789
|
+
"source": {
|
|
1790
|
+
"type": "string",
|
|
1791
|
+
"enum": [
|
|
1792
|
+
"dom",
|
|
1793
|
+
"served"
|
|
1794
|
+
],
|
|
1795
|
+
"description": "Page source to read; 'served' currently returns not-implemented."
|
|
1796
|
+
},
|
|
1797
|
+
"clean": {
|
|
1798
|
+
"type": "string",
|
|
1799
|
+
"enum": [
|
|
1800
|
+
"standard",
|
|
1801
|
+
"aggressive",
|
|
1802
|
+
"none"
|
|
1803
|
+
],
|
|
1804
|
+
"description": "HTML cleaning level."
|
|
1805
|
+
},
|
|
1806
|
+
"maxChars": {
|
|
1807
|
+
"type": "integer",
|
|
1808
|
+
"description": "Character cap on the cleaned HTML; default 300000."
|
|
1809
|
+
},
|
|
1810
|
+
"timeoutMs": {
|
|
1811
|
+
"type": "integer",
|
|
1812
|
+
"description": "Timeout in milliseconds; default 90000, max 300000."
|
|
1813
|
+
},
|
|
1814
|
+
"prompt": {
|
|
1815
|
+
"type": "string",
|
|
1816
|
+
"enum": [
|
|
1817
|
+
"html",
|
|
1818
|
+
"schematron"
|
|
1819
|
+
],
|
|
1820
|
+
"description": "Prompt shape: 'html' (default) sends cleaned HTML only; 'schematron' uses the open-weight Schematron model-card prompt with the schema inline (or CDP_EXTRACT_PROMPT)."
|
|
1821
|
+
},
|
|
1822
|
+
"model": {
|
|
1823
|
+
"type": "string",
|
|
1824
|
+
"description": "Extraction model name; default 'schematron' (or CDP_EXTRACT_MODEL)."
|
|
1825
|
+
},
|
|
1826
|
+
"baseUrl": {
|
|
1827
|
+
"type": "string",
|
|
1828
|
+
"description": "OpenAI-compatible endpoint base URL; default local loopback (or CDP_EXTRACT_BASE_URL)."
|
|
1829
|
+
},
|
|
1830
|
+
"savePath": {
|
|
1831
|
+
"type": "string",
|
|
1832
|
+
"description": "Output file path."
|
|
1833
|
+
}
|
|
1834
|
+
},
|
|
1835
|
+
"required": [
|
|
1836
|
+
"schema"
|
|
1837
|
+
],
|
|
1838
|
+
"additionalProperties": false
|
|
1839
|
+
}
|
|
1702
1840
|
}
|
|
1703
1841
|
];
|
package/src/toolDocs.ts
CHANGED
|
@@ -173,6 +173,26 @@ export const TOOL_DOCS: Record<string, ToolDoc> = {
|
|
|
173
173
|
"modifiers": "Modifier keys held for this one event, e.g. ['Shift']."
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
|
+
"focus_emulation": {
|
|
177
|
+
"description": "Toggles CDP Emulation.setFocusEmulationEnabled on a tab: the page then BELIEVES it is focused (document.hasFocus()→true, visibilitychange stays 'visible', focus events fire) while the real OS window and active tab stay put — no focus theft. This is the primitive that unlocks focus-GATED UI (a button the site disables unless its tab is focused, e.g. Anthropic's Claude Code OAuth Authorize button on claude.ai/oauth/*) on a background tab. Chrome-only: WebDriver BiDi's emulation module has no focus primitive, so this tool is absent from tools/list under --browser firefox. Ports the core mechanism of the danbuhler/claude-code-auto-authorize extension. Remember to call with enabled:false to restore, or use click_focus_gated which restores automatically.",
|
|
178
|
+
"params": {
|
|
179
|
+
"lease": "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions.",
|
|
180
|
+
"target": "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'.",
|
|
181
|
+
"enabled": "true = page reports focused/visible; false = restore the real focus state."
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"click_focus_gated": {
|
|
185
|
+
"description": "One-call compose of focus emulation + trusted click for a focus-gated button: enables Emulation.setFocusEmulationEnabled, polls until the button (located by CSS selector OR exact visible-text match, e.g. 'Authorize') exists AND is enabled, dispatches a trusted move/press/release click via Input.dispatchMouseEvent (isTrusted:true — a content script's synthesized events can never satisfy a focus gate), then restores focus emulation off unless keepFocus. Built for the Claude Code OAuth Authorize button but works on any focus-gated control on a background tab. Throws with a diagnostic ('never located' vs 'found but stayed disabled') on timeout. Chrome-only; absent under --browser firefox.",
|
|
186
|
+
"params": {
|
|
187
|
+
"lease": "Lease token from claim_page. Omit for a tab this process holds. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease).",
|
|
188
|
+
"target": "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'.",
|
|
189
|
+
"selector": "CSS selector of the button. Give exactly one of selector or text.",
|
|
190
|
+
"text": "Exact visible-text match of the button (trims whitespace, matches button/[role=button]/submit inputs), e.g. 'Authorize'. Give exactly one of selector or text.",
|
|
191
|
+
"timeoutMs": "Max ms to poll for the button to appear and become enabled. Default 30000.",
|
|
192
|
+
"pollMs": "Poll interval in ms. Default 500.",
|
|
193
|
+
"keepFocus": "Leave focus emulation ON after the click (default false — restored even on throw, so a failed click never strands the page fake-focused)."
|
|
194
|
+
}
|
|
195
|
+
},
|
|
176
196
|
"wait_for_download": {
|
|
177
197
|
"description": "Wait for a file download to finish and return it as a real file on disk: {path,suggestedFilename,bytes,url,target}, written under the artifact dir's downloads/. ORDERING RULE, not optional: capture must be ARMED BEFORE the click that starts the download — call wait_for_download{arm:true} first (returns {armed:true,downloadPath,pending}), then click, then call again to collect the file. The download-behavior override is per-connection state Chrome REVERTS the moment the arming client disconnects, and an unarmed headless Chrome denies the download outright, so one triggered before arming is lost. SIDE EFFECT, browser-global: arming redirects EVERY download in this browser (all tabs, all origins) into the toolkit's downloads dir until this server exits — they no longer land in the user's normal Downloads folder. The arm lives on a connection this server holds open (an MCP-server capability): under the one-shot CLI the connection dies with the process and nothing is captured. Chrome-only (capability 'browser.downloads'): absent from tools/list under Firefox.",
|
|
178
198
|
"params": {
|
|
@@ -464,5 +484,22 @@ export const TOOL_DOCS: Record<string, ToolDoc> = {
|
|
|
464
484
|
"list_leases": {
|
|
465
485
|
"description": "Enumerate every active lease for diagnosis - backend, target id, label, pid, createdAt, lastUsedAt, ttlMs, whether the owning process is alive, and whether the lease is reclaimable and why. No token required; it never returns the lease nonce, so it cannot be used to forge one. An unreadable or unparseable lease row instead carries unreadable, with label, pid and timestamps zeroed and stale forced false (an unreadable lease must never read as free to take). Under CDP_REQUIRE_LEASE it reaps abandoned agent tabs exactly as list_pages does, into an additive reaped array, omitting their rows. Where the tab can answer, a row also carries humanActiveMs, ms since input this server did not dispatch - note lastUsedAt tracks TOOLKIT calls only, so a tab a person has typed in for ten minutes looks idle, which is what this exposes; the field is absent, never null, when the tab cannot answer, and that absence means 'no answer', never 'nobody is there'. idleMs (now - lastUsedAt) and expiresAt (lastUsedAt + ttlMs) are computed fresh each call and omitted on an unreadable row.",
|
|
466
486
|
"params": {}
|
|
487
|
+
},
|
|
488
|
+
"extract_page": {
|
|
489
|
+
"description": "Structured extraction: take the target page's HTML, clean it, and send it to an OpenAI-compatible /chat/completions endpoint along with your JSON Schema, getting back schema-conformant JSON instead of prose. THE DEFAULT ENDPOINT IS LOOPBACK - llm-ferry at http://127.0.0.1:8090/v1 with model 'schematron' - so page content stays ON THIS MACHINE unless the operator overrides baseUrl (or sets CDP_EXTRACT_BASE_URL). Configuration: CDP_EXTRACT_BASE_URL (endpoint override), CDP_EXTRACT_MODEL (model override), CDP_EXTRACT_API_KEY (bearer token; never logged and REDACTED from every error), CDP_EXTRACT_TIMEOUT_MS, CDP_EXTRACT_MAX_CHARS, CDP_EXTRACT_PROMPT. PROMPT SHAPE: by default only the cleaned HTML is sent and the schema rides in response_format, which is what the HOSTED Schematron API expects (it injects the schema server-side); set prompt:'schematron' when baseUrl points at a LOCALLY SERVED open-weight Schematron (e.g. an MLX schematron8B), because that model was fine-tuned on a prompt with the schema INSIDE the user message and an HTML-only prompt returns garbage from it even under constrained decoding. THE SCHEMA-DESCRIPTION RULE: this is prompt-driven extraction, and the extraction INSTRUCTIONS live in the schema's property descriptions - write them like prompts ('the job title, exactly as printed', 'ISO date, not 'yesterday'). A schema whose properties carry no descriptions is REJECTED up front with an error saying so, because it would produce garbage extraction with no hint why. Source: 'dom' (live DOM, the rendered, JS-executed page) is the default; 'served' (the raw server response before rendering) currently returns a not-implemented error rather than guessing. Size guard: cleaned HTML over maxChars (default 300000) fails with html_too_large naming the actual size - shrink the payload with 'selector' to scope to a subtree, clean:'aggressive' to strip harder (standard already drops scripts/styles/hidden), or a larger maxChars if you truly need it all. Endpoint failures surface as upstream_error (with status and body, API key redacted) or timeout; the response never contains the key. A completion the endpoint cut off at its 8192-token output cap fails naming finish_reason=length and the fix (narrow with 'selector', or ask the schema for less) - it is NOT reported as invalid JSON, because a truncated tail is a size problem, not a model one. COST IS ALWAYS VISIBLE: every successful call reports the endpoint's usage token counts inline, so a slow/bloated extraction is visible in the answer itself, not discovered on a bill later.",
|
|
490
|
+
"params": {
|
|
491
|
+
"lease": "Lease token from claim_page. Omit for a tab this process holds; required for a tab held by another process or one claimed explicitly. Auto-acquired under CDP_REQUIRE_LEASE (then pass target, not lease). See server instructions.",
|
|
492
|
+
"target": "Page selector: 'active' (default), 'index:N', 'url:<substr>', 'title:<substr>', 'label:<name>', or a 32-hex '<targetId>'. Grammar: see server instructions.",
|
|
493
|
+
"schema": "REQUIRED JSON Schema object (type object) describing the JSON you want back. This is both the contract AND the prompt: each property's 'description' tells the extractor what to put there, so make them specific instructions. A schema with no property descriptions at all is rejected before any page work happens - empty descriptions extract garbage and the error says exactly what to fix. Nested objects and arrays of objects work; their inner descriptions count too.",
|
|
494
|
+
"selector": "CSS selector scoping extraction to that subtree's HTML only (pierces open shadow roots). The cheapest fix for html_too_large: extract the article or table, not the whole page. Omit to send the full cleaned document.",
|
|
495
|
+
"source": "'dom' (default) reads the live, rendered DOM - what the page shows after JS ran. 'served' would read the raw HTTP response before rendering; it currently returns a not-implemented error naming the arm, so callers can detect and fall back rather than guessing.",
|
|
496
|
+
"clean": "HTML cleaning level before the payload is measured and sent. 'standard' (default) strips scripts, styles, hidden elements and other non-content noise. 'aggressive' additionally collapses whitespace, drops nav/footer/boilerplate - use when html_too_large bites or the extractor chases junk. 'none' sends the raw HTML; almost never what you want.",
|
|
497
|
+
"maxChars": "Character cap on the cleaned HTML payload. Default 300000 (also settable via CDP_EXTRACT_MAX_CHARS). Exceeding it fails with html_too_large reporting actual vs cap - it does NOT silently truncate, because a half page extracted as though it were whole is a wrong answer that looks right.",
|
|
498
|
+
"timeoutMs": "Total budget in ms for the endpoint call. Default 90000, hard max 300000 (CDP_EXTRACT_TIMEOUT_MS overrides the default). Expiry fails with a timeout error, never a hang.",
|
|
499
|
+
"prompt": "Prompt shape sent upstream. 'html' (default, also CDP_EXTRACT_PROMPT=html) sends the cleaned HTML as the only user message and lets response_format carry the schema - correct for the hosted Schematron API, which injects the schema server-side. 'schematron' sends the open-weight Schematron model card's own messages: a 'You are a helpful assistant' system message plus a user message with the compact JSON-stringified schema inline, then the HTML, then 'MAKE SURE ITS VALID JSON.' - use it when baseUrl points at a locally served open-weight Schematron. It adds roughly the stringified schema's length to the prompt (maxChars caps the HTML only); response_format is sent in both modes. The arg wins over CDP_EXTRACT_PROMPT, and any other value is rejected up front.",
|
|
500
|
+
"model": "Model name for the endpoint call. Default 'schematron' on the loopback llm-ferry; override per-call or via CDP_EXTRACT_MODEL.",
|
|
501
|
+
"baseUrl": "OpenAI-compatible endpoint base URL (the server appends /chat/completions). DEFAULT IS http://127.0.0.1:8090/v1 - loopback llm-ferry, page content never leaves the machine. Setting this (or CDP_EXTRACT_BASE_URL) to a remote host is an operator decision about where page content may travel; the response never echoes the URL with credentials.",
|
|
502
|
+
"savePath": "Write the extracted JSON here instead of returning it inline: the response then carries the file path and usage counts only, keeping large payloads out of the transcript. An absolute path (starting with /) is used as-is; a relative path resolves under the artifact dir (/tmp/cdp-toolkit); missing parents are created."
|
|
503
|
+
}
|
|
467
504
|
}
|
|
468
505
|
};
|
package/src/toolGroups.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const TOOL_GROUPS = [
|
|
2
2
|
"core", "input", "cookies", "network", "console", "mocking",
|
|
3
3
|
"emulation", "performance", "recording", "leases", "permissions",
|
|
4
|
-
"dialogs", "downloads",
|
|
4
|
+
"dialogs", "downloads", "extraction",
|
|
5
5
|
] as const;
|
|
6
6
|
|
|
7
7
|
export type ToolGroup = (typeof TOOL_GROUPS)[number];
|
|
@@ -14,7 +14,7 @@ export const GROUP_TOOLS: Record<ToolGroup, readonly string[]> = {
|
|
|
14
14
|
],
|
|
15
15
|
input: [
|
|
16
16
|
"hover", "drag", "scroll", "dispatch_mouse", "press_key", "fill_form",
|
|
17
|
-
"upload_file",
|
|
17
|
+
"upload_file", "focus_emulation", "click_focus_gated",
|
|
18
18
|
],
|
|
19
19
|
cookies: ["list_cookies", "set_cookie", "delete_cookies"],
|
|
20
20
|
network: ["list_network_requests", "get_network_request"],
|
|
@@ -31,6 +31,7 @@ export const GROUP_TOOLS: Record<ToolGroup, readonly string[]> = {
|
|
|
31
31
|
permissions: ["grant_permissions"],
|
|
32
32
|
dialogs: ["handle_dialog"],
|
|
33
33
|
downloads: ["wait_for_download"],
|
|
34
|
+
extraction: ["extract_page"],
|
|
34
35
|
} as const;
|
|
35
36
|
|
|
36
37
|
export const TOOL_GROUP: Record<string, ToolGroup> = {};
|