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 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-16, Chrome 153, isolated headless, 8 wedges) |
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 **2ms** |
50
- | Wedged `navigate` → dead endpoint (8×) | **rejected at p50 15.03s, max 15.03s** — the configured bound, never a hang |
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 **89ms** |
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`, 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.
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 ? coerce(rawValue) : true]);
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.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.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`, with the value coerced:
13
- * "true"/"false" -> boolean, a numeric string -> number, else the raw
14
- * string. A bare `--flag` with no following value (or followed by another
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
- const flagPairs: Array<[string, unknown]> = [];
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 ? coerce(rawValue) : true]);
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) out.args[k] = v;
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
@@ -6,4 +6,4 @@
6
6
  * ready line; keeping it in its own module means a release bump touches one
7
7
  * line here plus package.json, not a const buried in the server.
8
8
  */
9
- export const VERSION = "3.0.0";
9
+ export const VERSION = "3.0.2";