cdp-toolkit 3.0.0 → 3.0.2
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 +12 -0
- package/README.md +5 -5
- package/dist/cli.js +14 -3
- package/dist/mcp.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +27 -7
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to cdp-toolkit are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.0.2] - 2026-10-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- CLI: `get_network_request --requestId <id>` failed with "no network request matched" for every Fetch/XHR request. CDP mints those ids as `<pid>.<n>` (e.g. `91775.34`), and the CLI coerced any numeric-looking flag value to a number, so the lookup compared a number against the stored string, and `91775.10` was mangled to `91775.1`. Flag coercion now follows the tool's schema: a key typed as a string keeps its raw text, and every other key is coerced as before. Verified live on headless Chrome 154: all 6 captured requests resolve by `--requestId` from both the source and the built CLI; on 3.0.1 the 5 Fetch requests failed and only the hex-id Document request resolved. MCP callers were not affected, since their arguments arrive already typed. (`src/cli.ts`, `test/cli-coerce.test.ts`)
|
|
13
|
+
|
|
14
|
+
## [3.0.1] - 2026-09-29
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `bench:wedge` now drives the headless Chrome it spawns (free port, HeadlessChrome check); it previously drove CDP_BASE/9222, so the 2026-09-16 README numbers were not from an isolated instance. Re-measured.
|
|
19
|
+
|
|
8
20
|
## [3.0.0] - 2026-09-29
|
|
9
21
|
|
|
10
22
|
**The default MCP tool listing went from 49 tools to 7 (37,346 bytes to 5,072, -86.4%; about 9,336 tokens to about 1,268).** With `CDP_TOOL_PROFILE` unset, `tools/list` now advertises `search_tools`, `call_tool` and five core tools (`navigate_page`, `evaluate_script`, `take_snapshot`, `click`, `fill`); every other tool is found with `search_tools` and run with `call_tool`. `CDP_TOOL_PROFILE=full` restores the 2.x listing (plus the two meta-tools). The 48 underlying tools and the `cdp` CLI are unchanged, and the listing is still static: computed once at startup, `listChanged:false`, same cache hints.
|
package/README.md
CHANGED
|
@@ -44,12 +44,12 @@ If [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp
|
|
|
44
44
|
|
|
45
45
|
Every number above is runnable. `bun run bench:wedge` spawns an isolated headless Chrome, points a throwaway page at an endpoint that accepts the connection and never responds, and measures the full stuck-page lifecycle (re-run with `CDP_TIMEOUT_MS=3000` for a ~60s sample; defaults below use the 15s bound):
|
|
46
46
|
|
|
47
|
-
| Phase | Measured (2026-09-
|
|
47
|
+
| Phase | Measured (2026-09-29, HeadlessChrome 154.0.8037.59, isolated headless, 8 wedges) |
|
|
48
48
|
|---|---|
|
|
49
|
-
| Healthy `evaluate_script` (50×) | p50 **1ms**, p95 **
|
|
50
|
-
| Wedged `navigate` → dead endpoint (8×) | **rejected at p50 15.
|
|
49
|
+
| Healthy `evaluate_script` (50×) | p50 **1ms**, p95 **3ms** |
|
|
50
|
+
| Wedged `navigate` → dead endpoint (8×) | **rejected at p50 15.01s, max 15.02s** — the configured bound, never a hang |
|
|
51
51
|
| Witness tab evaluated mid-brick (8×) | p95 **3ms** — other tabs never stall |
|
|
52
|
-
| Recovery: close bricked tab + reopen + evaluate (8×) | p95 **
|
|
52
|
+
| Recovery: close bricked tab + reopen + evaluate (8×) | p95 **33ms** |
|
|
53
53
|
| `/mcp` restarts · server restarts | **0** · **0** |
|
|
54
54
|
|
|
55
55
|
One honest caveat the benchmark also reports: while a navigation to a dead endpoint is pending, *that page* stays unresponsive to new sessions — the blast radius is the tab, not the server. Closing it is a browser-level command (2ms) and a fresh page answers at healthy latency; your other tabs, and the MCP server, never noticed. Exit code is nonzero unless every wedge rejected in-bound and every recovery landed under 1s, so the benchmark is CI-able.
|
|
@@ -218,7 +218,7 @@ cdp take_screenshot --target url:example --renderWidth 1920 --renderHeight 1080
|
|
|
218
218
|
cdp lighthouse_audit --url https://example.com --json '{"categories":["performance"]}'
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
**Argument parsing:** the first positional token is the tool name. `--json '<obj>'` merges a JSON object into the args (applied first). `--target <sel>` sets `args.target`. Repeated `--key value` pairs become `args.key
|
|
221
|
+
**Argument parsing:** the first positional token is the tool name. `--json '<obj>'` merges a JSON object into the args (applied first). `--target <sel>` sets `args.target`. Repeated `--key value` pairs become `args.key`. A key the tool's schema types as a string keeps its raw text (so `--requestId 91775.10` stays `"91775.10"`); any other key is coerced (`true`/`false` → boolean, numeric strings → number, else string). A bare `--flag` is `true`. Explicit flags override keys from `--json`. Output is `JSON.stringify(result, null, 2)` on stdout (exit 0); on any throw, `{"error":"<message>"}` goes to stderr and the process exits 1.
|
|
222
222
|
|
|
223
223
|
**`--help`/`-h`** are recognized anywhere in argv, ahead of every other flag, and are never treated as a tool argument or a tool name — as of 1.9.3, `<tool> --help` used to run the tool instead. With no tool named, `cdp --help` prints the usage above. With a tool named, `cdp <tool> --help` prints that tool's arguments (name, type, required/optional, description) read from its schema, and exits 0 having made no CDP connection, taken no lease, and written no file.
|
|
224
224
|
|
package/dist/cli.js
CHANGED
|
@@ -11370,6 +11370,13 @@ function coerce(raw) {
|
|
|
11370
11370
|
}
|
|
11371
11371
|
return raw;
|
|
11372
11372
|
}
|
|
11373
|
+
function coerceForTool(tool, key, raw) {
|
|
11374
|
+
const spec = tool === undefined ? undefined : MANIFEST.find((t) => t.name === tool);
|
|
11375
|
+
const prop = spec?.inputSchema.properties?.[key];
|
|
11376
|
+
if (prop?.type === "string")
|
|
11377
|
+
return raw;
|
|
11378
|
+
return coerce(raw);
|
|
11379
|
+
}
|
|
11373
11380
|
function parseArgv(argv) {
|
|
11374
11381
|
const out = { list: false, capabilities: false, help: false, args: {} };
|
|
11375
11382
|
let jsonObj;
|
|
@@ -11407,7 +11414,7 @@ function parseArgv(argv) {
|
|
|
11407
11414
|
flagPairs.push(["target", rawValue]);
|
|
11408
11415
|
continue;
|
|
11409
11416
|
}
|
|
11410
|
-
flagPairs.push([key, hasValue ?
|
|
11417
|
+
flagPairs.push([key, hasValue ? rawValue : true]);
|
|
11411
11418
|
continue;
|
|
11412
11419
|
}
|
|
11413
11420
|
if (out.tool === undefined)
|
|
@@ -11415,8 +11422,9 @@ function parseArgv(argv) {
|
|
|
11415
11422
|
}
|
|
11416
11423
|
if (jsonObj)
|
|
11417
11424
|
Object.assign(out.args, jsonObj);
|
|
11418
|
-
for (const [k, v] of flagPairs)
|
|
11419
|
-
out.args[k] = v;
|
|
11425
|
+
for (const [k, v] of flagPairs) {
|
|
11426
|
+
out.args[k] = v === true || k === "target" ? v : coerceForTool(out.tool, k, v);
|
|
11427
|
+
}
|
|
11420
11428
|
return out;
|
|
11421
11429
|
}
|
|
11422
11430
|
function isToolName(name) {
|
|
@@ -11559,3 +11567,6 @@ if (isDirectRun()) {
|
|
|
11559
11567
|
process.exit(1);
|
|
11560
11568
|
});
|
|
11561
11569
|
}
|
|
11570
|
+
export {
|
|
11571
|
+
parseArgv
|
|
11572
|
+
};
|
package/dist/mcp.js
CHANGED
|
@@ -11369,7 +11369,7 @@ var TOOL_DOCS = {
|
|
|
11369
11369
|
};
|
|
11370
11370
|
|
|
11371
11371
|
// src/version.ts
|
|
11372
|
-
var VERSION = "3.0.
|
|
11372
|
+
var VERSION = "3.0.2";
|
|
11373
11373
|
|
|
11374
11374
|
// src/mcp.ts
|
|
11375
11375
|
function isDirectRun() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-toolkit",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
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 (only 5 core tools listed by default, plus search_tools/call_tool to reach the rest) 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",
|
package/src/cli.ts
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
* - First positional token is the tool name (a key of TOOLS).
|
|
10
10
|
* - `--json '<obj>'` parses a JSON object and merges it into the args object.
|
|
11
11
|
* - `--target <sel>` sets `args.target` (a TargetSelector string).
|
|
12
|
-
* - Any other `--key value` pair becomes `args.key
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - Any other `--key value` pair becomes `args.key`. When the tool's MANIFEST
|
|
13
|
+
* inputSchema declares the key `type: "string"`, the raw string is kept
|
|
14
|
+
* as-is (so a CDP requestId like "91775.10" is not turned into the number
|
|
15
|
+
* 91775.1). Otherwise the value is coerced: "true"/"false" -> boolean, a
|
|
16
|
+
* numeric string -> number, else the raw string. A bare `--flag` with no following value (or followed by another
|
|
15
17
|
* --flag) is treated as boolean true.
|
|
16
18
|
* - Explicit `--key` pairs OVERRIDE keys merged from `--json` (last writer
|
|
17
19
|
* wins; --json is applied first, then the individual flags).
|
|
@@ -108,6 +110,20 @@ function coerce(raw: string): boolean | number | string {
|
|
|
108
110
|
return raw;
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Coerce one `--key value` using the tool's declared schema type. A key the
|
|
115
|
+
* schema types as "string" keeps its raw text verbatim; coercing it would turn
|
|
116
|
+
* an id that merely LOOKS numeric ("91775.10", "007") into a number that no
|
|
117
|
+
* longer matches, or into a different string entirely. Every other key (or a
|
|
118
|
+
* tool/key the manifest doesn't know) falls back to coerce().
|
|
119
|
+
*/
|
|
120
|
+
function coerceForTool(tool: string | undefined, key: string, raw: string): boolean | number | string {
|
|
121
|
+
const spec = tool === undefined ? undefined : MANIFEST.find((t) => t.name === tool);
|
|
122
|
+
const prop = spec?.inputSchema.properties?.[key] as { type?: unknown } | undefined;
|
|
123
|
+
if (prop?.type === "string") return raw;
|
|
124
|
+
return coerce(raw);
|
|
125
|
+
}
|
|
126
|
+
|
|
111
127
|
interface ParsedArgs {
|
|
112
128
|
tool?: string;
|
|
113
129
|
list: boolean;
|
|
@@ -117,12 +133,14 @@ interface ParsedArgs {
|
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
/** Parse process argv (already sliced to drop the runtime + script, and with --browser already stripped). */
|
|
120
|
-
function parseArgv(argv: string[]): ParsedArgs {
|
|
136
|
+
export function parseArgv(argv: string[]): ParsedArgs {
|
|
121
137
|
const out: ParsedArgs = { list: false, capabilities: false, help: false, args: {} };
|
|
122
138
|
// Collect flag pairs separately so --json can be merged BEFORE individual
|
|
123
139
|
// flags override it, regardless of token order.
|
|
124
140
|
let jsonObj: Record<string, unknown> | undefined;
|
|
125
|
-
|
|
141
|
+
// Raw values are kept until the tool name is known (it may follow the flags),
|
|
142
|
+
// so coercion can consult that tool's schema.
|
|
143
|
+
const flagPairs: Array<[string, string | true]> = [];
|
|
126
144
|
|
|
127
145
|
for (let i = 0; i < argv.length; i++) {
|
|
128
146
|
const token = argv[i]!;
|
|
@@ -164,7 +182,7 @@ function parseArgv(argv: string[]): ParsedArgs {
|
|
|
164
182
|
flagPairs.push(["target", rawValue]);
|
|
165
183
|
continue;
|
|
166
184
|
}
|
|
167
|
-
flagPairs.push([key, hasValue ?
|
|
185
|
+
flagPairs.push([key, hasValue ? rawValue : true]);
|
|
168
186
|
continue;
|
|
169
187
|
}
|
|
170
188
|
// First non-flag positional is the tool name; ignore any extras.
|
|
@@ -173,7 +191,9 @@ function parseArgv(argv: string[]): ParsedArgs {
|
|
|
173
191
|
|
|
174
192
|
// --json first, then explicit flags win.
|
|
175
193
|
if (jsonObj) Object.assign(out.args, jsonObj);
|
|
176
|
-
for (const [k, v] of flagPairs)
|
|
194
|
+
for (const [k, v] of flagPairs) {
|
|
195
|
+
out.args[k] = v === true || k === "target" ? v : coerceForTool(out.tool, k, v);
|
|
196
|
+
}
|
|
177
197
|
return out;
|
|
178
198
|
}
|
|
179
199
|
|
package/src/version.ts
CHANGED