cdp-mcp 0.1.3 → 0.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.
Files changed (50) hide show
  1. package/README.md +67 -31
  2. package/dist/contract.d.ts +11 -0
  3. package/dist/contract.js +11 -0
  4. package/dist/contract.js.map +1 -0
  5. package/dist/index.d.ts +18 -0
  6. package/dist/locator.d.ts +108 -0
  7. package/dist/locator.js +176 -0
  8. package/dist/locator.js.map +1 -0
  9. package/dist/server.d.ts +2 -0
  10. package/dist/server.js +4 -0
  11. package/dist/server.js.map +1 -1
  12. package/dist/session/browser.d.ts +29 -0
  13. package/dist/session/browser.js +17 -2
  14. package/dist/session/browser.js.map +1 -1
  15. package/dist/session/buffers.d.ts +48 -0
  16. package/dist/session/pause.d.ts +21 -0
  17. package/dist/session/state.d.ts +53 -0
  18. package/dist/sourcemap/loader.d.ts +4 -0
  19. package/dist/sourcemap/normalize.d.ts +2 -0
  20. package/dist/sourcemap/store.d.ts +57 -0
  21. package/dist/tools/_locator_runtime.d.ts +31 -0
  22. package/dist/tools/_locator_runtime.js +243 -0
  23. package/dist/tools/_locator_runtime.js.map +1 -0
  24. package/dist/tools/_register.d.ts +2 -0
  25. package/dist/tools/breakpoints.d.ts +4 -0
  26. package/dist/tools/console.d.ts +2 -0
  27. package/dist/tools/dom.d.ts +2 -0
  28. package/dist/tools/dom.js +3 -221
  29. package/dist/tools/dom.js.map +1 -1
  30. package/dist/tools/execution.d.ts +29 -0
  31. package/dist/tools/forms.d.ts +8 -0
  32. package/dist/tools/forms.js +256 -0
  33. package/dist/tools/forms.js.map +1 -0
  34. package/dist/tools/inspect.d.ts +2 -0
  35. package/dist/tools/nav.d.ts +2 -0
  36. package/dist/tools/network.d.ts +2 -0
  37. package/dist/tools/session.d.ts +2 -0
  38. package/dist/tools/session.js +1 -1
  39. package/dist/tools/session.js.map +1 -1
  40. package/dist/tools/source.d.ts +2 -0
  41. package/dist/tools/storage.d.ts +2 -0
  42. package/dist/tools/storage.js +296 -0
  43. package/dist/tools/storage.js.map +1 -0
  44. package/dist/util/browser-resolve.d.ts +19 -0
  45. package/dist/util/errors.d.ts +7 -0
  46. package/dist/util/format.d.ts +20 -0
  47. package/dist/util/log.d.ts +6 -0
  48. package/docs/chromium-sandboxing.md +6 -0
  49. package/docs/local-l3-e2e-setup.md +199 -0
  50. package/package.json +13 -1
package/README.md CHANGED
@@ -6,9 +6,11 @@ Designed for agents running in CLIs (Claude Code, GitHub Copilot CLI) that have
6
6
 
7
7
  **Status:** alpha. **License:** [MIT](./LICENSE).
8
8
 
9
+ **Last updated: 2026-06-09**
10
+
9
11
  ## What it gives an agent
10
12
 
11
- Across 39 tools:
13
+ Across 48 tools:
12
14
 
13
15
  - **Breakpoints in TS source** — `set_breakpoint(file="src/foo.ts", line=42, condition?, log_message?)`. The server matches source maps and binds in every script that maps back to that file.
14
16
  - **Stepping** — `step_over`, `step_into`, `step_out`, `resume`, `pause`, plus the authoritative sync point `wait_for_pause`.
@@ -16,6 +18,8 @@ Across 39 tools:
16
18
  - **Buffered console + network** — pull-based, paginated by monotonic `seq`. Bodies are lazy-loaded via `get_request_body` / `get_response_body`.
17
19
  - **Light DOM interaction** — `query_selector`, `click`, `type_text`, `press_key`, `screenshot` so the agent can drive a flow to a breakpoint.
18
20
  - **Structured DOM querying** — Playwright-inspired `locate` (LocatorSpec: CSS, text, role, test-id, label, placeholder, name), `wait_for` (poll until DOM state), `get_form_state` (read named form fields).
21
+ - **Form driving** — `fill`, `check` / `uncheck`, `select_option`, plus `suggest_locator` to get a robust semantic locator for an element.
22
+ - **Session portability** — `export_storage_state` / `load_storage_state` carry a logged-in session (cookies + localStorage) across runs; `get_cookies` / `set_cookies` read and set cookies directly (`get_cookies` redacts likely-auth / HttpOnly values for safe logging).
19
23
  - **Source-map diagnostics** — `list_scripts`, `resolve_source_position`, `get_script_source`.
20
24
 
21
25
  Auto-attaches to iframes and workers via `Target.setAutoAttach({ flatten: true })`.
@@ -67,9 +71,11 @@ SSE mode caveats:
67
71
  tears down client A's session).
68
72
  - **Non-loopback bind requires opt-in.** `--allow-remote` (or
69
73
  `CDP_MCP_ALLOW_REMOTE=1`) is required to bind to anything other than
70
- loopback. MCP tools include `evaluate` (in-page code exec) and a
71
- `screenshot path=` filesystem write; the gate makes remote exposure
72
- a deliberate operator decision rather than a default.
74
+ loopback. MCP tools include `evaluate` (in-page code exec), a
75
+ `screenshot path=` filesystem write, `export_storage_state` (writes full
76
+ cookie values including HttpOnly auth secrets — to a server-side file) and
77
+ `load_storage_state` (reads an arbitrary server-side file); the gate makes
78
+ remote exposure a deliberate operator decision rather than a default.
73
79
  - **Host / Origin headers are validated on loopback binds** to block
74
80
  DNS-rebinding against `127.0.0.1` / `localhost` / `[::1]`. On
75
81
  non-loopback binds the operator has already accepted exposure via
@@ -101,10 +107,10 @@ grader/trace/oracle units. See `docs/test-eval-plan.md` for the full pyramid.
101
107
  npm run test:e2e
102
108
  ```
103
109
 
104
- Drives the 39 MCP tools against a real headless Chromium attached to a
105
- built copy of `examples/sample-app/`. Nine specs cover lifecycle, breakpoints,
106
- stepping, exceptions, console, network, workers, screenshot, and DOM
107
- interaction. Sequential (one Chrome shared across specs, isolated by a
110
+ Drives the 48 MCP tools against a real headless Chromium attached to a
111
+ built copy of `examples/sample-app/`. Eleven specs cover lifecycle, breakpoints,
112
+ stepping, exceptions, console, network, workers, screenshot, DOM
113
+ interaction, form driving, and storage portability. Sequential (one Chrome shared across specs, isolated by a
108
114
  shared `afterEach(close_session)`). Run time is a few seconds on a warm
109
115
  machine.
110
116
 
@@ -140,14 +146,17 @@ pretest hook) enforces this.
140
146
  `launch_chrome` defaults to `--no-sandbox` for Ubuntu/Playwright-Chromium
141
147
  compatibility. See [`docs/chromium-sandboxing.md`](docs/chromium-sandboxing.md)
142
148
  before changing that default or relying on `sandbox: true`, AppArmor, snap
143
- confinement, or Bubblewrap.
149
+ confinement, or Bubblewrap. For the step-by-step setup that gets local
150
+ `npm run test:e2e` passing with the sandbox **on** (install Playwright Chromium
151
+ + attach the AppArmor profile), see
152
+ [`docs/local-l3-e2e-setup.md`](docs/local-l3-e2e-setup.md).
144
153
 
145
154
  ### L4 — LLM agent evals
146
155
 
147
156
  ```sh
148
157
  export ANTHROPIC_API_KEY=...
149
- npm run eval:quick # 1 scenario × 1 trial (~$0.50–2 at default Opus-4.7-medium; ~$0.05 with EVAL_MODEL_OVERRIDE=claude-sonnet-4-6)
150
- npm run eval # all scenarios × 3 trials (first observed ~$4 at default Opus-4.7-medium on a reference host one data point, not the steady-state band)
158
+ npm run eval:quick # 1 scenario × 1 trial (~$0.50–2 at default Opus-4.8-medium; ~$0.05 with EVAL_MODEL_OVERRIDE=claude-sonnet-4-6)
159
+ npm run eval # all scenarios × 3 trials (~$4 full pass — first observed on Opus-4.7-medium, the prior default; 4.8 shares its rate card)
151
160
  npm run eval -- --scenarios=compute-step --trials=1
152
161
  ```
153
162
 
@@ -155,8 +164,10 @@ Use `npm run eval` (or `npm run eval:quick`) — NOT `npx tsx evals/cli.ts` dire
155
164
 
156
165
  Drives the cdp-mcp tool surface through an LLM agent via the
157
166
  `VendorAdapter` seam (`evals/harness/vendor.ts`); the Anthropic adapter
158
- backed by `@anthropic-ai/sdk` is the default and only production path
159
- today. Each trial spawns a fresh `dist/index.js` MCP subprocess + a
167
+ backed by `@anthropic-ai/sdk` is the default; OpenAI, Vertex, DeepSeek,
168
+ and Moonshot/Kimi are also shipped production adapters (plus an LM Studio
169
+ reference adapter for local models), each selected via `EVAL_PROVIDER`.
170
+ Each trial spawns a fresh `dist/index.js` MCP subprocess + a
160
171
  static server for the scenario's sample-app variant; the tool-use loop
161
172
  drives the page, sets source-level breakpoints, inspects pauses, and
162
173
  produces a natural-language final answer. NDJSON traces land under
@@ -165,7 +176,7 @@ produces a natural-language final answer. NDJSON traces land under
165
176
  exercise the debugger workflow under test) + **correctness** (did the
166
177
  final answer name the bug) — plus efficiency ratio and recovery count.
167
178
 
168
- **Default model**: `claude-opus-4-7` with adaptive thinking at
179
+ **Default model**: `claude-opus-4-8` with adaptive thinking at
169
180
  `effort=medium` (set in `evals/harness/model.ts`). Adaptive-style models
170
181
  (Opus 4.7+) default to medium-effort thinking when no env override is
171
182
  set; budget-style models (Sonnet 4.6, selectable via
@@ -199,23 +210,27 @@ each `t:"usage"` trace entry (the Anthropic adapter populates
199
210
  `cacheTokens.cacheReadInputTokens` and `cacheTokens.cacheCreationInputTokens`
200
211
  verbatim from the SDK's `cache_read_input_tokens` / `cache_creation_input_tokens`).
201
212
 
202
- Non-Anthropic backends: the OpenAI vendor adapter ships with #50/#58
203
- (target: GPT-5.5) — `EVAL_PROVIDER=openai` plus `OPENAI_API_KEY`
204
- + `EVAL_OPENAI_MODEL` activates it. Reasoning-off trials route to
205
- `/v1/chat/completions` (#50); reasoning-on trials route to
206
- `/v1/responses` (#58), the only OpenAI surface that supports tools
207
- × reasoning_effort on GPT-5.5. An LM Studio investigation artifact
208
- is wired behind the same seam for issue #45. See
209
- [evals/README.md](evals/README.md) for full `EVAL_PROVIDER` /
210
- `EVAL_OPENAI_*` / `EVAL_LM_STUDIO_*` details. Vertex (#51) is the last
211
- backend adapter still pending.
212
-
213
- Currently registered scenarios (8): `compute-step` and
214
- `adversarial-out-of-order` ship against the canonical `examples/sample-app/`;
215
- `network-bug`, `console-error`, `event-binding`, `deep-source-map`,
216
- `worker-bug`, and `conditional-bp` have committed per-scenario forks
217
- under `evals/sample-app-variants/<name>/` and build via
218
- `npm run sample:build` (`scripts/build-variants.mjs`).
213
+ Non-Anthropic backends ship behind the same seam, each selected via
214
+ `EVAL_PROVIDER`: OpenAI / GPT-5.5 (#50/#58) — reasoning-off trials route to
215
+ `/v1/chat/completions` (#50), reasoning-on trials to `/v1/responses` (#58),
216
+ the only OpenAI surface that supports tools × reasoning_effort on GPT-5.5;
217
+ Vertex / Gemini (#51); and DeepSeek + Moonshot/Kimi (GH #8), remote
218
+ OpenAI-compatible `/v1` vendors. An LM Studio investigation artifact is
219
+ wired behind the seam for local models (issue #45). See
220
+ [evals/README.md](evals/README.md) for full `EVAL_PROVIDER` / `EVAL_OPENAI_*`
221
+ / `EVAL_VERTEX_*` / `EVAL_DEEPSEEK_*` / `EVAL_MOONSHOT_*` / `EVAL_LM_STUDIO_*`
222
+ details.
223
+
224
+ Currently registered scenarios (14) 8 **debugger** scenarios
225
+ (`compute-step`, `adversarial-out-of-order`, `network-bug`, `console-error`,
226
+ `event-binding`, `deep-source-map`, `worker-bug`, `conditional-bp`) plus 6
227
+ **driving + session-portability** scenarios from issue #12 (`form-drive`,
228
+ `clearing-fill`, `idempotent-toggle`, `robust-locator`, `session-resume`,
229
+ `cookie-redaction`). `compute-step` is the canonical `npm run eval:quick`
230
+ target; some scenarios run against the stock `examples/sample-app/`, others
231
+ against per-scenario forks under `evals/sample-app-variants/<name>/` built via
232
+ `npm run sample:build` (`scripts/build-variants.mjs`). See
233
+ [evals/README.md](evals/README.md) for the full scenario table.
219
234
 
220
235
  ## Wire into Claude Code
221
236
 
@@ -253,6 +268,27 @@ Or via `~/.claude.json`:
253
268
  - **Errors** come back as `isError: true` with a structured `{ error, message }` JSON payload.
254
269
  - **Compact returns**: previews trimmed to ~200 chars, lists capped at sensible defaults — bodies lazy-loaded via dedicated tools.
255
270
 
271
+ ## Programmatic contract (`cdp-mcp/contract`)
272
+
273
+ The structured `LocatorSpec` that `locate`, `wait_for`, and the form-driving tools
274
+ accept is published as a side-effect-free subpath export, so external tooling can
275
+ *produce and validate* specs without duplicating the shape or pulling in the CLI:
276
+
277
+ ```ts
278
+ import { locatorSchema, parseLocator, serializeLocator } from "cdp-mcp/contract";
279
+ import type { LocatorSpec } from "cdp-mcp/contract";
280
+
281
+ const spec = parseLocator({ by: "role", role: "button", name: "Submit" });
282
+ locatorSchema.parse(spec); // throws on an invalid shape
283
+ serializeLocator(spec); // stable, normalized JSON
284
+ ```
285
+
286
+ Exports: `LocatorSpec` (type), `LocatorBy`, `locatorSchema` / `locatorShape` /
287
+ `locatorBySchema` (Zod), and `normalizeLocator` / `parseLocator` / `serializeLocator`
288
+ / `LocatorError`. This module imports only `zod`. The subpath is **ESM-only** (the
289
+ `exports` map defines `import`, not `require`) — consume it from an ESM module or a
290
+ bundler.
291
+
256
292
  ## Prior art
257
293
 
258
294
  If `cdp-mcp` doesn't fit your workflow, look at:
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public package contract, published under the `cdp-mcp/contract` subpath export.
3
+ *
4
+ * Keep this a thin, side-effect-free barrel: it must only re-export from modules
5
+ * (like `./locator.js`) whose import graph never reaches the CLI/server entry
6
+ * (`./index.js`, `./server.js`, `./session/*`). That guarantee is what lets a
7
+ * downstream consumer `import { locatorSchema } from "cdp-mcp/contract"` without
8
+ * dragging in the executable's transport/shebang side effects.
9
+ */
10
+ export { LocatorError, locatorBySchema, locatorShape, locatorSchema, normalizeLocator, parseLocator, serializeLocator, } from "./locator.js";
11
+ export type { LocatorBy, LocatorSpec } from "./locator.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public package contract, published under the `cdp-mcp/contract` subpath export.
3
+ *
4
+ * Keep this a thin, side-effect-free barrel: it must only re-export from modules
5
+ * (like `./locator.js`) whose import graph never reaches the CLI/server entry
6
+ * (`./index.js`, `./server.js`, `./session/*`). That guarantee is what lets a
7
+ * downstream consumer `import { locatorSchema } from "cdp-mcp/contract"` without
8
+ * dragging in the executable's transport/shebang side effects.
9
+ */
10
+ export { LocatorError, locatorBySchema, locatorShape, locatorSchema, normalizeLocator, parseLocator, serializeLocator, } from "./locator.js";
11
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EACL,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { type IncomingMessage, type ServerResponse } from "node:http";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export interface SseClient {
6
+ server: McpServer;
7
+ transport: SSEServerTransport;
8
+ }
9
+ export declare function handleSseRequest({ req, res, clients, host, port, validateHostOrigin, allowedHosts, allowedOrigins, }: {
10
+ req: IncomingMessage;
11
+ res: ServerResponse;
12
+ clients: Map<string, SseClient>;
13
+ host: string;
14
+ port: number;
15
+ validateHostOrigin: boolean;
16
+ allowedHosts: Set<string>;
17
+ allowedOrigins: Set<string>;
18
+ }): Promise<void>;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Canonical `LocatorSpec` contract for cdp-mcp.
3
+ *
4
+ * This module is the single source of truth for the structured element-locator
5
+ * shape that `locate`, `wait_for`, and the form-driving tools accept. It is
6
+ * deliberately **side-effect free** and depends only on `zod`, so external
7
+ * consumers can import it (via `cdp-mcp/contract`) to produce and validate specs
8
+ * without pulling in the CLI/server. Tool code re-imports these symbols rather
9
+ * than redefining them, so the published contract can never silently drift from
10
+ * what the tools actually accept.
11
+ */
12
+ import { z } from "zod";
13
+ /**
14
+ * Error thrown by {@link normalizeLocator} / {@link parseLocator} for an invalid
15
+ * spec. `code` mirrors the cdp-mcp tool error codes so tool handlers can re-wrap it
16
+ * structurally: `"missing_arg"` when a spec is under-specified for its strategy,
17
+ * `"invalid_locator"` for an unsupported strategy.
18
+ */
19
+ export declare class LocatorError extends Error {
20
+ readonly code: string;
21
+ constructor(message: string, code?: string);
22
+ }
23
+ /** The locator strategies cdp-mcp understands. `css` is the default when a selector is given. */
24
+ export declare const locatorBySchema: z.ZodEnum<["css", "text", "role", "test_id", "testId", "label", "placeholder", "name"]>;
25
+ /**
26
+ * The raw Zod shape for a LocatorSpec. Spread into tool input schemas
27
+ * (`{ ...locatorShape, ... }`) so the field docs stay identical everywhere.
28
+ */
29
+ export declare const locatorShape: {
30
+ by: z.ZodOptional<z.ZodEnum<["css", "text", "role", "test_id", "testId", "label", "placeholder", "name"]>>;
31
+ selector: z.ZodOptional<z.ZodString>;
32
+ css: z.ZodOptional<z.ZodString>;
33
+ text: z.ZodOptional<z.ZodString>;
34
+ role: z.ZodOptional<z.ZodString>;
35
+ name: z.ZodOptional<z.ZodString>;
36
+ test_id: z.ZodOptional<z.ZodString>;
37
+ testId: z.ZodOptional<z.ZodString>;
38
+ label: z.ZodOptional<z.ZodString>;
39
+ placeholder: z.ZodOptional<z.ZodString>;
40
+ exact: z.ZodOptional<z.ZodBoolean>;
41
+ };
42
+ /** A standalone `ZodObject` for a LocatorSpec — external consumers can `.parse()`/`.safeParse()`. */
43
+ export declare const locatorSchema: z.ZodObject<{
44
+ by: z.ZodOptional<z.ZodEnum<["css", "text", "role", "test_id", "testId", "label", "placeholder", "name"]>>;
45
+ selector: z.ZodOptional<z.ZodString>;
46
+ css: z.ZodOptional<z.ZodString>;
47
+ text: z.ZodOptional<z.ZodString>;
48
+ role: z.ZodOptional<z.ZodString>;
49
+ name: z.ZodOptional<z.ZodString>;
50
+ test_id: z.ZodOptional<z.ZodString>;
51
+ testId: z.ZodOptional<z.ZodString>;
52
+ label: z.ZodOptional<z.ZodString>;
53
+ placeholder: z.ZodOptional<z.ZodString>;
54
+ exact: z.ZodOptional<z.ZodBoolean>;
55
+ }, "strip", z.ZodTypeAny, {
56
+ css?: string | undefined;
57
+ text?: string | undefined;
58
+ role?: string | undefined;
59
+ test_id?: string | undefined;
60
+ testId?: string | undefined;
61
+ label?: string | undefined;
62
+ placeholder?: string | undefined;
63
+ name?: string | undefined;
64
+ exact?: boolean | undefined;
65
+ by?: "css" | "text" | "role" | "test_id" | "testId" | "label" | "placeholder" | "name" | undefined;
66
+ selector?: string | undefined;
67
+ }, {
68
+ css?: string | undefined;
69
+ text?: string | undefined;
70
+ role?: string | undefined;
71
+ test_id?: string | undefined;
72
+ testId?: string | undefined;
73
+ label?: string | undefined;
74
+ placeholder?: string | undefined;
75
+ name?: string | undefined;
76
+ exact?: boolean | undefined;
77
+ by?: "css" | "text" | "role" | "test_id" | "testId" | "label" | "placeholder" | "name" | undefined;
78
+ selector?: string | undefined;
79
+ }>;
80
+ export type LocatorBy = z.infer<typeof locatorBySchema>;
81
+ export interface LocatorSpec {
82
+ by?: LocatorBy;
83
+ selector?: string;
84
+ css?: string;
85
+ text?: string;
86
+ role?: string;
87
+ name?: string;
88
+ test_id?: string;
89
+ testId?: string;
90
+ label?: string;
91
+ placeholder?: string;
92
+ exact?: boolean;
93
+ }
94
+ /**
95
+ * Validate and canonicalize a spec: infer `by` from selector/css, enforce the
96
+ * required field for the chosen strategy, and fold the `name` fallback into the
97
+ * strategy-specific field. Throws {@link LocatorError} on an under-specified spec.
98
+ */
99
+ export declare function normalizeLocator(input: LocatorSpec): LocatorSpec;
100
+ /** Validate an unknown value as a LocatorSpec (shape + strategy requirements). */
101
+ export declare function parseLocator(input: unknown): LocatorSpec;
102
+ /**
103
+ * Serialize a LocatorSpec to a stable, normalized JSON string. Equivalent specs
104
+ * serialize identically regardless of which alias the caller used — e.g.
105
+ * `{ css: ".x" }` and `{ selector: ".x" }` both yield `{"by":"css","selector":".x"}`
106
+ * — so the output is safe to use as a cache key or for equality checks.
107
+ */
108
+ export declare function serializeLocator(spec: LocatorSpec): string;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Canonical `LocatorSpec` contract for cdp-mcp.
3
+ *
4
+ * This module is the single source of truth for the structured element-locator
5
+ * shape that `locate`, `wait_for`, and the form-driving tools accept. It is
6
+ * deliberately **side-effect free** and depends only on `zod`, so external
7
+ * consumers can import it (via `cdp-mcp/contract`) to produce and validate specs
8
+ * without pulling in the CLI/server. Tool code re-imports these symbols rather
9
+ * than redefining them, so the published contract can never silently drift from
10
+ * what the tools actually accept.
11
+ */
12
+ import { z } from "zod";
13
+ /**
14
+ * Error thrown by {@link normalizeLocator} / {@link parseLocator} for an invalid
15
+ * spec. `code` mirrors the cdp-mcp tool error codes so tool handlers can re-wrap it
16
+ * structurally: `"missing_arg"` when a spec is under-specified for its strategy,
17
+ * `"invalid_locator"` for an unsupported strategy.
18
+ */
19
+ export class LocatorError extends Error {
20
+ code;
21
+ constructor(message, code = "missing_arg") {
22
+ super(message);
23
+ this.name = "LocatorError";
24
+ this.code = code;
25
+ }
26
+ }
27
+ /** The locator strategies cdp-mcp understands. `css` is the default when a selector is given. */
28
+ export const locatorBySchema = z.enum([
29
+ "css",
30
+ "text",
31
+ "role",
32
+ "test_id",
33
+ "testId",
34
+ "label",
35
+ "placeholder",
36
+ "name",
37
+ ]);
38
+ /**
39
+ * The raw Zod shape for a LocatorSpec. Spread into tool input schemas
40
+ * (`{ ...locatorShape, ... }`) so the field docs stay identical everywhere.
41
+ */
42
+ export const locatorShape = {
43
+ by: locatorBySchema.optional().describe("Locator strategy. Omit when passing selector/css for a CSS lookup."),
44
+ selector: z.string().optional().describe("CSS selector. Equivalent to by=css."),
45
+ css: z.string().optional().describe("CSS selector. Equivalent to selector."),
46
+ text: z.string().optional().describe("Text to match for by=text."),
47
+ role: z.string().optional().describe("ARIA/implicit role for by=role, e.g. button, link, textbox."),
48
+ name: z.string().optional().describe("Accessible name, field name, or fallback value depending on the locator strategy."),
49
+ test_id: z.string().optional().describe("Value for data-testid, data-test-id, or data-test."),
50
+ // Both snake_case (`test_id`) and camelCase (`testId`) are accepted so callers
51
+ // can use whichever matches their convention; `normalizeLocator` /
52
+ // `serializeLocator` fold them to the canonical `test_id`. Likewise `name` is a
53
+ // cross-strategy fallback that gets folded into the strategy-specific field.
54
+ testId: z.string().optional().describe("CamelCase alias for test_id."),
55
+ label: z.string().optional().describe("Label text for by=label."),
56
+ placeholder: z.string().optional().describe("Placeholder text for by=placeholder."),
57
+ exact: z.boolean().optional().describe("Default false: substring match for text/name-like fields."),
58
+ };
59
+ /** A standalone `ZodObject` for a LocatorSpec — external consumers can `.parse()`/`.safeParse()`. */
60
+ export const locatorSchema = z.object(locatorShape);
61
+ /**
62
+ * Validate and canonicalize a spec: infer `by` from selector/css, enforce the
63
+ * required field for the chosen strategy, and fold the `name` fallback into the
64
+ * strategy-specific field. Throws {@link LocatorError} on an under-specified spec.
65
+ */
66
+ export function normalizeLocator(input) {
67
+ const by = input.by ?? (input.selector || input.css ? "css" : undefined);
68
+ if (!by)
69
+ throw new LocatorError("by is required unless selector/css is supplied");
70
+ switch (by) {
71
+ case "css": {
72
+ const selector = input.selector ?? input.css;
73
+ if (!selector)
74
+ throw new LocatorError("selector or css is required for by=css");
75
+ return { ...input, by, selector };
76
+ }
77
+ case "role":
78
+ if (!input.role)
79
+ throw new LocatorError("role is required for by=role");
80
+ return { ...input, by };
81
+ case "text":
82
+ if (!input.text && !input.name)
83
+ throw new LocatorError("text or name is required for by=text");
84
+ return { ...input, by, text: input.text ?? input.name };
85
+ case "test_id":
86
+ case "testId": {
87
+ const testId = input.test_id ?? input.testId ?? input.name;
88
+ if (!testId)
89
+ throw new LocatorError(`test_id, testId, or name is required for by=${by}`);
90
+ return { ...input, by, test_id: testId };
91
+ }
92
+ case "label": {
93
+ const label = input.label ?? input.name;
94
+ if (!label)
95
+ throw new LocatorError("label or name is required for by=label");
96
+ return { ...input, by, label };
97
+ }
98
+ case "placeholder": {
99
+ const placeholder = input.placeholder ?? input.name;
100
+ if (!placeholder)
101
+ throw new LocatorError("placeholder or name is required for by=placeholder");
102
+ return { ...input, by, placeholder };
103
+ }
104
+ case "name":
105
+ if (!input.name)
106
+ throw new LocatorError("name is required for by=name");
107
+ return { ...input, by };
108
+ default: {
109
+ // Compile-time exhaustiveness: if `locatorBySchema` gains a strategy and a
110
+ // case here is missed, `by` is no longer `never` and this fails to build
111
+ // (pairs with `noFallthroughCasesInSwitch`).
112
+ const _exhaustive = by;
113
+ throw new LocatorError(`unsupported locator strategy: ${String(_exhaustive)}`, "invalid_locator");
114
+ }
115
+ }
116
+ }
117
+ /** Validate an unknown value as a LocatorSpec (shape + strategy requirements). */
118
+ export function parseLocator(input) {
119
+ return normalizeLocator(locatorSchema.parse(input));
120
+ }
121
+ /**
122
+ * Serialize a LocatorSpec to a stable, normalized JSON string. Equivalent specs
123
+ * serialize identically regardless of which alias the caller used — e.g.
124
+ * `{ css: ".x" }` and `{ selector: ".x" }` both yield `{"by":"css","selector":".x"}`
125
+ * — so the output is safe to use as a cache key or for equality checks.
126
+ */
127
+ export function serializeLocator(spec) {
128
+ return JSON.stringify(canonicalLocator(spec));
129
+ }
130
+ /**
131
+ * Reduce a spec to only its canonical fields, in a fixed key order, dropping
132
+ * alias inputs (`css`, `testId`, and the cross-strategy `name` fallback). This is
133
+ * what makes {@link serializeLocator} stable across equivalent inputs — the raw
134
+ * `normalizeLocator` result still carries whichever aliases the caller passed.
135
+ */
136
+ function canonicalLocator(spec) {
137
+ const n = normalizeLocator(spec);
138
+ const out = {};
139
+ switch (n.by) {
140
+ case "css":
141
+ out.by = "css";
142
+ out.selector = n.selector;
143
+ break;
144
+ case "role":
145
+ out.by = "role";
146
+ out.role = n.role;
147
+ if (n.name !== undefined)
148
+ out.name = n.name;
149
+ break;
150
+ case "text":
151
+ out.by = "text";
152
+ out.text = n.text;
153
+ break;
154
+ case "test_id":
155
+ case "testId":
156
+ out.by = "test_id";
157
+ out.test_id = n.test_id;
158
+ break;
159
+ case "label":
160
+ out.by = "label";
161
+ out.label = n.label;
162
+ break;
163
+ case "placeholder":
164
+ out.by = "placeholder";
165
+ out.placeholder = n.placeholder;
166
+ break;
167
+ case "name":
168
+ out.by = "name";
169
+ out.name = n.name;
170
+ break;
171
+ }
172
+ if (n.exact !== undefined)
173
+ out.exact = n.exact;
174
+ return out;
175
+ }
176
+ //# sourceMappingURL=locator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locator.js","sourceRoot":"","sources":["../src/locator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function buildServer(): McpServer;
package/dist/server.js CHANGED
@@ -8,6 +8,8 @@ import { registerInspectTools } from "./tools/inspect.js";
8
8
  import { registerConsoleTools } from "./tools/console.js";
9
9
  import { registerNetworkTools } from "./tools/network.js";
10
10
  import { registerDomTools } from "./tools/dom.js";
11
+ import { registerFormTools } from "./tools/forms.js";
12
+ import { registerStorageTools } from "./tools/storage.js";
11
13
  export function buildServer() {
12
14
  const server = new McpServer({
13
15
  name: "cdp-mcp",
@@ -22,6 +24,8 @@ export function buildServer() {
22
24
  registerConsoleTools(server);
23
25
  registerNetworkTools(server);
24
26
  registerDomTools(server);
27
+ registerFormTools(server);
28
+ registerStorageTools(server);
25
29
  // The SDK advertises `tools: { listChanged: true }` as soon as any tool is
26
30
  // registered, but never emits the matching notification on its own. Some
27
31
  // clients (e.g. GitHub Copilot CLI over SSE) gate their first `tools/list`
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,UAAU,WAAW;IACzB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAEzB,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,CAAC,MAAM,CAAC,aAAa,GAAG,GAAG,EAAE;QACjC,MAAM,CAAC,mBAAmB,EAAE,CAAC;IAC/B,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,UAAU,WAAW;IACzB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE7B,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,CAAC,MAAM,CAAC,aAAa,GAAG,GAAG,EAAE;QACjC,MAAM,CAAC,mBAAmB,EAAE,CAAC;IAC/B,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ export interface LaunchArgs {
2
+ url?: string;
3
+ headless?: boolean;
4
+ userDataDir?: string;
5
+ args?: string[];
6
+ chromePath?: string;
7
+ sandbox?: boolean;
8
+ }
9
+ export interface AttachArgs {
10
+ port?: number;
11
+ host?: string;
12
+ targetFilter?: {
13
+ type?: string;
14
+ urlIncludes?: string;
15
+ };
16
+ }
17
+ export declare function launchChrome(opts?: LaunchArgs): Promise<{
18
+ targetId: string;
19
+ url: string;
20
+ }>;
21
+ export declare function attachChrome(opts?: AttachArgs): Promise<{
22
+ targetId: string;
23
+ url: string;
24
+ }>;
25
+ export declare function closeSession(): Promise<void>;
26
+ export declare function switchTarget(targetId: string): Promise<{
27
+ targetId: string;
28
+ url: string;
29
+ }>;
@@ -20,9 +20,24 @@ export async function launchChrome(opts = {}) {
20
20
  // (stale) port → ECONNREFUSED on every connect. Don't pass it; let
21
21
  // chrome-launcher own port selection. `runningChrome.port` then reflects
22
22
  // the actual port Chrome is listening on. (Codex blocker review on PR #11.)
23
- const useSandbox = opts.sandbox === true;
23
+ // Sandbox decision: an explicit `sandbox` arg from the caller always wins.
24
+ // When the caller omits it, fall back to the CDP_SANDBOX env (default off);
25
+ // "true" or "1" enable it (matching the eval runner's EVAL_SANDBOX parsing).
26
+ // This lets a host with a working sandbox path opt a whole run into
27
+ // sandbox-on (e.g. the L4 eval runner via EVAL_SANDBOX → CDP_SANDBOX)
28
+ // without prompt-injecting every launch_chrome call. Unset env → false →
29
+ // the --no-sandbox automation default (unchanged). Explicit `sandbox: false`
30
+ // still forces --no-sandbox even if the env is set.
31
+ const sandboxEnv = process.env.CDP_SANDBOX;
32
+ const useSandbox = opts.sandbox ?? (sandboxEnv === "true" || sandboxEnv === "1");
24
33
  const userArgs = opts.args ?? [];
25
34
  const userAlreadyDisabled = userArgs.includes("--no-sandbox");
35
+ // A caller can request the sandbox AND still pass --no-sandbox in args; the
36
+ // userArgs spread re-adds it last, so Chromium ends up unsandboxed despite the
37
+ // request. Warn rather than silently dropping the sandbox.
38
+ if (useSandbox && userAlreadyDisabled) {
39
+ log.warn("launch_chrome: sandbox requested but --no-sandbox is in args; the flag wins and the sandbox stays OFF");
40
+ }
26
41
  // Snap-confinement auto-profile. When the effective Chrome path (explicit
27
42
  // chromePath, or CHROME_PATH env that chrome-launcher will pick up) is
28
43
  // under /snap/ AND the caller didn't already specify userDataDir, derive
@@ -57,7 +72,7 @@ export async function launchChrome(opts = {}) {
57
72
  const chrome = await launch(launchOpts);
58
73
  sessionState.chrome = chrome;
59
74
  sessionState.chromePort = chrome.port;
60
- log.info("launched chrome", { port: chrome.port, pid: chrome.pid });
75
+ log.info("launched chrome", { port: chrome.port, pid: chrome.pid, sandbox: useSandbox });
61
76
  // Pick the first page target.
62
77
  const targets = await waitForFirstPage(chrome.port);
63
78
  const target = targets[0];