cdp-mcp 0.1.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/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/index.js +345 -0
- package/dist/index.js.map +1 -0
- package/dist/server.js +27 -0
- package/dist/server.js.map +1 -0
- package/dist/session/browser.js +394 -0
- package/dist/session/browser.js.map +1 -0
- package/dist/session/buffers.js +43 -0
- package/dist/session/buffers.js.map +1 -0
- package/dist/session/pause.js +99 -0
- package/dist/session/pause.js.map +1 -0
- package/dist/session/state.js +93 -0
- package/dist/session/state.js.map +1 -0
- package/dist/sourcemap/loader.js +138 -0
- package/dist/sourcemap/loader.js.map +1 -0
- package/dist/sourcemap/normalize.js +59 -0
- package/dist/sourcemap/normalize.js.map +1 -0
- package/dist/sourcemap/store.js +185 -0
- package/dist/sourcemap/store.js.map +1 -0
- package/dist/tools/_register.js +30 -0
- package/dist/tools/_register.js.map +1 -0
- package/dist/tools/breakpoints.js +164 -0
- package/dist/tools/breakpoints.js.map +1 -0
- package/dist/tools/console.js +48 -0
- package/dist/tools/console.js.map +1 -0
- package/dist/tools/dom.js +527 -0
- package/dist/tools/dom.js.map +1 -0
- package/dist/tools/execution.js +89 -0
- package/dist/tools/execution.js.map +1 -0
- package/dist/tools/inspect.js +178 -0
- package/dist/tools/inspect.js.map +1 -0
- package/dist/tools/nav.js +136 -0
- package/dist/tools/nav.js.map +1 -0
- package/dist/tools/network.js +137 -0
- package/dist/tools/network.js.map +1 -0
- package/dist/tools/session.js +76 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/source.js +63 -0
- package/dist/tools/source.js.map +1 -0
- package/dist/util/browser-resolve.js +263 -0
- package/dist/util/browser-resolve.js.map +1 -0
- package/dist/util/errors.js +12 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/format.js +65 -0
- package/dist/util/format.js.map +1 -0
- package/dist/util/log.js +34 -0
- package/dist/util/log.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leonard Janke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# cdp-mcp
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that exposes the Chrome DevTools Protocol (CDP) to AI agents as a **TypeScript-aware frontend debugger**.
|
|
4
|
+
|
|
5
|
+
Designed for agents running in CLIs (Claude Code, GitHub Copilot CLI) that have local source + source-map access. Coordinates flow in TS terms; the server translates to JS for CDP under the hood.
|
|
6
|
+
|
|
7
|
+
**Status:** alpha (v0.1.0). **License:** [MIT](./LICENSE).
|
|
8
|
+
|
|
9
|
+
## What it gives an agent
|
|
10
|
+
|
|
11
|
+
Across 39 tools:
|
|
12
|
+
|
|
13
|
+
- **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
|
+
- **Stepping** — `step_over`, `step_into`, `step_out`, `resume`, `pause`, plus the authoritative sync point `wait_for_pause`.
|
|
15
|
+
- **Live inspection at a paused frame** — `get_call_stack`, `get_scope`, `evaluate` (frame-aware), `get_object_properties`. All call-stack frames are TS-mapped.
|
|
16
|
+
- **Buffered console + network** — pull-based, paginated by monotonic `seq`. Bodies are lazy-loaded via `get_request_body` / `get_response_body`.
|
|
17
|
+
- **Light DOM interaction** — `query_selector`, `click`, `type_text`, `press_key`, `screenshot` so the agent can drive a flow to a breakpoint.
|
|
18
|
+
- **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).
|
|
19
|
+
- **Source-map diagnostics** — `list_scripts`, `resolve_source_position`, `get_script_source`.
|
|
20
|
+
|
|
21
|
+
Auto-attaches to iframes and workers via `Target.setAutoAttach({ flatten: true })`.
|
|
22
|
+
|
|
23
|
+
## Install / build
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
node dist/index.js # stdio MCP transport (default — this is what Claude Code launches)
|
|
29
|
+
node dist/index.js --port 9719 # SSE MCP transport on 127.0.0.1:9719
|
|
30
|
+
node dist/index.js --host 0.0.0.0 --port 9719 --allow-remote
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
SSE mode caveats:
|
|
34
|
+
|
|
35
|
+
- **Single-client only.** Every `/sse` connection gets its own `McpServer`,
|
|
36
|
+
but every tool funnels through one process-global `sessionState` — two
|
|
37
|
+
concurrent clients race on the same browser (shared pause state,
|
|
38
|
+
breakpoints, console/network buffers; `launch_chrome` from client B
|
|
39
|
+
tears down client A's session).
|
|
40
|
+
- **Non-loopback bind requires opt-in.** `--allow-remote` (or
|
|
41
|
+
`CDP_MCP_ALLOW_REMOTE=1`) is required to bind to anything other than
|
|
42
|
+
loopback. MCP tools include `evaluate` (in-page code exec) and a
|
|
43
|
+
`screenshot path=` filesystem write; the gate makes remote exposure
|
|
44
|
+
a deliberate operator decision rather than a default.
|
|
45
|
+
- **Host / Origin headers are validated on loopback binds** to block
|
|
46
|
+
DNS-rebinding against `127.0.0.1` / `localhost` / `[::1]`. On
|
|
47
|
+
non-loopback binds the operator has already accepted exposure via
|
|
48
|
+
`--allow-remote`, and the server can't statically enumerate every
|
|
49
|
+
hostname/IP a LAN/VPN/DNS client might reach it by — those checks
|
|
50
|
+
are skipped. If you need per-`Host` policy on a LAN/WAN deployment,
|
|
51
|
+
front the server with a reverse proxy that enforces it.
|
|
52
|
+
|
|
53
|
+
Smoke test (no browser needed — verifies the protocol surface):
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npm run smoke
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Unit + L2 contract tests (~640ms, no browser, no LLM):
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
npm test
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Currently 299 tests across 22 files. The `test/` tree is the L2 contract
|
|
66
|
+
layer (every tool exercised against a fake CDP — see `test/fake-cdp.ts`);
|
|
67
|
+
the inline `src/**/*.test.ts` files are L1 pure-data tests; `evals/**/
|
|
68
|
+
*.test.ts` cover the L4 harness's grader/trace/oracle units (21 tests).
|
|
69
|
+
See `docs/test-eval-plan.md` for the full pyramid.
|
|
70
|
+
|
|
71
|
+
### L3 — real-browser end-to-end
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npm run test:e2e
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Drives the 39 MCP tools against a real headless Chromium attached to a
|
|
78
|
+
built copy of `examples/sample-app/`. Nine specs cover lifecycle, breakpoints,
|
|
79
|
+
stepping, exceptions, console, network, workers, screenshot, and DOM
|
|
80
|
+
interaction. Sequential (one Chrome shared across specs, isolated by a
|
|
81
|
+
shared `afterEach(close_session)`). Run time is a few seconds on a warm
|
|
82
|
+
machine.
|
|
83
|
+
|
|
84
|
+
**Browser selection (`CDP_TEST_BROWSER` env, default `chromium`)**:
|
|
85
|
+
|
|
86
|
+
| Linux x86_64 | Linux ARM64 (primary local) | macOS | Windows |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `chromium`: Playwright's bundled binary, system chromium, or apt | `chromium`: Playwright's bundled binary or apt (`/snap/bin/chromium` honored with snap-confinement userDataDir workaround) | `chromium`: Homebrew / Playwright bundled | `chromium`: Playwright bundled (set `CDP_TEST_BROWSER_PATH`) |
|
|
89
|
+
| `chrome`: chrome-launcher auto-detect | **not supported** — fail-fast | `chrome`: chrome-launcher auto-detect | `chrome`: chrome-launcher auto-detect |
|
|
90
|
+
|
|
91
|
+
**Local-Windows status**: at the time L3 landed, `chrome-launcher` 1.2.1 fails
|
|
92
|
+
to bind to its own picked port on Windows 11 (ECONNREFUSED inside chrome-
|
|
93
|
+
launcher's startup poll) regardless of headless mode, Chrome stable vs
|
|
94
|
+
Playwright Chromium, or explicit ports. The same code path works on Linux
|
|
95
|
+
where CI runs. If you need to test L3 changes locally on Windows, run them
|
|
96
|
+
under WSL2 (Ubuntu) or push and let CI validate. The unit + L2 tests work
|
|
97
|
+
fine on Windows.
|
|
98
|
+
|
|
99
|
+
Setting an explicit binary path (for example, after running
|
|
100
|
+
`npx playwright install chromium` locally on Linux) lets the resolver skip
|
|
101
|
+
detection and use the bundled binary:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
export CDP_TEST_BROWSER_PATH="$HOME/.cache/ms-playwright/chromium-1223/chrome-linux/chrome"
|
|
105
|
+
npm run test:e2e
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Any spec failing on Chromium-only but passing on Chrome stable should land
|
|
109
|
+
with a `// @chromium-skip — <gap-id>` comment AND a row in
|
|
110
|
+
`docs/known-chromium-gaps.md` — `npm run lint:chromium-skips` (and the
|
|
111
|
+
pretest hook) enforces this.
|
|
112
|
+
|
|
113
|
+
`launch_chrome` defaults to `--no-sandbox` for Ubuntu/Playwright-Chromium
|
|
114
|
+
compatibility. See [`docs/chromium-sandboxing.md`](docs/chromium-sandboxing.md)
|
|
115
|
+
before changing that default or relying on `sandbox: true`, AppArmor, snap
|
|
116
|
+
confinement, or Bubblewrap.
|
|
117
|
+
|
|
118
|
+
### L4 — LLM agent evals
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
export ANTHROPIC_API_KEY=...
|
|
122
|
+
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)
|
|
123
|
+
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)
|
|
124
|
+
npm run eval -- --scenarios=compute-step --trials=1
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use `npm run eval` (or `npm run eval:quick`) — NOT `npx tsx evals/cli.ts` directly. The npm script triggers the `preeval` lifecycle hook which rebuilds `dist/index.js` (the MCP subprocess); calling tsx directly bypasses the hook and a fresh clone fails with `Cannot find module '.../dist/index.js'`. If you must invoke tsx directly, run `npm run build` first.
|
|
128
|
+
|
|
129
|
+
Drives the cdp-mcp tool surface through an LLM agent via the
|
|
130
|
+
`VendorAdapter` seam (`evals/harness/vendor.ts`); the Anthropic adapter
|
|
131
|
+
backed by `@anthropic-ai/sdk` is the default and only production path
|
|
132
|
+
today. Each trial spawns a fresh `dist/index.js` MCP subprocess + a
|
|
133
|
+
static server for the scenario's sample-app variant; the tool-use loop
|
|
134
|
+
drives the page, sets source-level breakpoints, inspects pauses, and
|
|
135
|
+
produces a natural-language final answer. NDJSON traces land under
|
|
136
|
+
`evals/runs/<run-id>/` (gitignored). A programmatic oracle per scenario
|
|
137
|
+
(no LLM judge) emits a dual-axis verdict — **mechanic** (did the agent
|
|
138
|
+
exercise the debugger workflow under test) + **correctness** (did the
|
|
139
|
+
final answer name the bug) — plus efficiency ratio and recovery count.
|
|
140
|
+
|
|
141
|
+
**Default model**: `claude-opus-4-7` with adaptive thinking at
|
|
142
|
+
`effort=medium` (set in `evals/harness/model.ts`). Adaptive-style models
|
|
143
|
+
(Opus 4.7+) default to medium-effort thinking when no env override is
|
|
144
|
+
set; budget-style models (Sonnet 4.6, selectable via
|
|
145
|
+
`EVAL_MODEL_OVERRIDE`) keep extended thinking **off** by default for the
|
|
146
|
+
cheap-baseline path. Override via env:
|
|
147
|
+
|
|
148
|
+
- `EVAL_MODEL_OVERRIDE=claude-sonnet-4-6` — switch to the budget-style
|
|
149
|
+
Sonnet baseline (no thinking by default; ~$5–10/full run).
|
|
150
|
+
- `EVAL_REASONING_LEVEL=none|low|medium|high|xhigh|max` — pick a tier
|
|
151
|
+
(or explicit `none` to disable on adaptive models). On budget-style
|
|
152
|
+
models each tier maps to a default `budget_tokens` in
|
|
153
|
+
`TIER_BUDGET_TOKENS` (high=16K). On adaptive models the tier maps
|
|
154
|
+
directly to Anthropic's `effort` parameter.
|
|
155
|
+
- `EVAL_REASONING_BUDGET=N` — override the budget on budget-style
|
|
156
|
+
models. Used alone the level is tagged `custom`; used alongside
|
|
157
|
+
`EVAL_REASONING_LEVEL` it overrides that tier's default.
|
|
158
|
+
|
|
159
|
+
Thinking-on runs are non-deterministic (Anthropic requires
|
|
160
|
+
`temperature=1` with `thinking`), so use `--trials >= 3` to characterize
|
|
161
|
+
variance. Cost-cap: `$100` per `npm run eval` invocation (override via
|
|
162
|
+
`EVAL_BUDGET_USD` env). Rotation across the Anthropic family + GPT-5.5
|
|
163
|
+
is a follow-up — see the proposal at
|
|
164
|
+
[`docs/eval-model-rotation-proposal.md`](docs/eval-model-rotation-proposal.md).
|
|
165
|
+
|
|
166
|
+
Caching: the system prompt + tool list are tagged `cache_control:
|
|
167
|
+
ephemeral`. The system block (~280 tokens) is below Anthropic's
|
|
168
|
+
~1024-token cache-breakpoint minimum, so only the ~5K-token tools array
|
|
169
|
+
actually caches across trials — that's enough to dominate the input
|
|
170
|
+
cost across trial 2+. Verify post-run via the `cacheTokens` field on
|
|
171
|
+
each `t:"usage"` trace entry (the Anthropic adapter populates
|
|
172
|
+
`cacheTokens.cacheReadInputTokens` and `cacheTokens.cacheCreationInputTokens`
|
|
173
|
+
verbatim from the SDK's `cache_read_input_tokens` / `cache_creation_input_tokens`).
|
|
174
|
+
|
|
175
|
+
Non-Anthropic backends: the OpenAI vendor adapter ships with #50/#58
|
|
176
|
+
(target: GPT-5.5) — `EVAL_PROVIDER=openai` plus `OPENAI_API_KEY`
|
|
177
|
+
+ `EVAL_OPENAI_MODEL` activates it. Reasoning-off trials route to
|
|
178
|
+
`/v1/chat/completions` (#50); reasoning-on trials route to
|
|
179
|
+
`/v1/responses` (#58), the only OpenAI surface that supports tools
|
|
180
|
+
× reasoning_effort on GPT-5.5. An LM Studio investigation artifact
|
|
181
|
+
is wired behind the same seam for issue #45. See
|
|
182
|
+
[evals/README.md](evals/README.md) for full `EVAL_PROVIDER` /
|
|
183
|
+
`EVAL_OPENAI_*` / `EVAL_LM_STUDIO_*` details. Vertex (#51) is the last
|
|
184
|
+
backend adapter still pending.
|
|
185
|
+
|
|
186
|
+
Currently registered scenarios (8): `compute-step` and
|
|
187
|
+
`adversarial-out-of-order` ship against the canonical `examples/sample-app/`;
|
|
188
|
+
`network-bug`, `console-error`, `event-binding`, `deep-source-map`,
|
|
189
|
+
`worker-bug`, and `conditional-bp` have committed per-scenario forks
|
|
190
|
+
under `evals/sample-app-variants/<name>/` and build via
|
|
191
|
+
`npm run sample:build` (`scripts/build-variants.mjs`).
|
|
192
|
+
|
|
193
|
+
## Wire into Claude Code
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
claude mcp add cdp-mcp node /absolute/path/to/dist/index.js
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Or via `~/.claude.json`:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"mcpServers": {
|
|
204
|
+
"cdp-mcp": { "command": "node", "args": ["/abs/path/dist/index.js"] }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## End-to-end smoke (with a browser)
|
|
210
|
+
|
|
211
|
+
1. Install the sample app's deps and start it:
|
|
212
|
+
```sh
|
|
213
|
+
cd examples/sample-app
|
|
214
|
+
npm install
|
|
215
|
+
npm run dev # listens on :5173
|
|
216
|
+
```
|
|
217
|
+
2. In a Claude Code session with `cdp-mcp` enabled, ask:
|
|
218
|
+
> Open localhost:5173 in a non-headless browser. Set a breakpoint at src/handlers.ts:7. Click #go. When it pauses, tell me what `step` is — and why the counter increments wrong.
|
|
219
|
+
3. The agent should chain: `launch_chrome` → `set_breakpoint` → `click` → `wait_for_pause` → `get_scope`/`evaluate` → `resume`, and conclude that `computeStep()` returns `2` instead of `1`.
|
|
220
|
+
|
|
221
|
+
## Tool conventions for agents
|
|
222
|
+
|
|
223
|
+
- **File coords are TS, 1-based lines, 0-based columns** unless the tool name ends in `_js` or takes a `script_id`.
|
|
224
|
+
- **Pause-only tools** (`get_call_stack`, `get_scope`, `evaluate` with `frame_index`): return `error: "not_paused"` if called outside a pause.
|
|
225
|
+
- **Buffered tools** (`get_console_logs`, `get_network_requests`): return a `cursor` (max `seq` seen). Pass it back as `since` to paginate.
|
|
226
|
+
- **Errors** come back as `isError: true` with a structured `{ error, message }` JSON payload.
|
|
227
|
+
- **Compact returns**: previews trimmed to ~200 chars, lists capped at sensible defaults — bodies lazy-loaded via dedicated tools.
|
|
228
|
+
|
|
229
|
+
## Prior art
|
|
230
|
+
|
|
231
|
+
If `cdp-mcp` doesn't fit your workflow, look at:
|
|
232
|
+
- [`InDate/cdp-tools-mcp`](https://github.com/InDate/cdp-tools-mcp)
|
|
233
|
+
- [`ScriptedAlchemy/devtools-debugger-mcp`](https://github.com/ScriptedAlchemy/devtools-debugger-mcp) (Node-focused)
|
|
234
|
+
- [`ChromeDevTools/chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp) (automation + console, no breakpoints)
|
|
235
|
+
|
|
236
|
+
## Out of scope for v1
|
|
237
|
+
|
|
238
|
+
Firefox / Safari, Node.js debugging, `Storage.*`, `Tracing.*`, `HeapProfiler.*`, concurrent multi-page debugging.
|
|
239
|
+
|
|
240
|
+
See [design notes](docs/design-notes.md) — original plan snapshot + a section on what reviewer iteration discovered.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { buildServer } from "./server.js";
|
|
6
|
+
import { getSession, sessionState } from "./session/state.js";
|
|
7
|
+
import { log } from "./util/log.js";
|
|
8
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
9
|
+
function isLoopbackHost(host) {
|
|
10
|
+
return LOOPBACK_HOSTS.has(host);
|
|
11
|
+
}
|
|
12
|
+
function parseArgs(args) {
|
|
13
|
+
let port;
|
|
14
|
+
let host = "127.0.0.1";
|
|
15
|
+
let allowRemote = process.env.CDP_MCP_ALLOW_REMOTE === "1";
|
|
16
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
17
|
+
const arg = args[index];
|
|
18
|
+
if (!arg) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg === "--help" || arg === "-h") {
|
|
22
|
+
process.stderr.write([
|
|
23
|
+
"Usage:",
|
|
24
|
+
" cdp-mcp # stdio MCP transport",
|
|
25
|
+
" cdp-mcp --port 9719 # SSE MCP transport on 127.0.0.1:9719",
|
|
26
|
+
" cdp-mcp --host 0.0.0.0 --port 9719 --allow-remote",
|
|
27
|
+
"",
|
|
28
|
+
"SSE mode caveats:",
|
|
29
|
+
" - Single-client only: concurrent /sse connections race on a",
|
|
30
|
+
" shared browser session (sessionState is process-global).",
|
|
31
|
+
" - Non-loopback bind requires --allow-remote (or",
|
|
32
|
+
" CDP_MCP_ALLOW_REMOTE=1). MCP tools include in-page eval and",
|
|
33
|
+
" server-filesystem writes; exposing them remotely without",
|
|
34
|
+
" further auth is a deliberate operator decision.",
|
|
35
|
+
" - Host / Origin headers are validated against the loopback",
|
|
36
|
+
" aliases (127.0.0.1, localhost, [::1]) to block",
|
|
37
|
+
" DNS-rebinding. On non-loopback binds the operator has",
|
|
38
|
+
" accepted exposure via --allow-remote, and we cannot",
|
|
39
|
+
" enumerate every reachable hostname/IP, so the checks",
|
|
40
|
+
" are skipped — front the server with a reverse proxy if",
|
|
41
|
+
" you need per-Host policy.",
|
|
42
|
+
"",
|
|
43
|
+
].join("\n"));
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
if (arg === "--port") {
|
|
47
|
+
const value = args[index + 1];
|
|
48
|
+
if (!value)
|
|
49
|
+
throw new Error("--port requires a value");
|
|
50
|
+
port = parsePort(value);
|
|
51
|
+
index += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg.startsWith("--port=")) {
|
|
55
|
+
port = parsePort(arg.slice("--port=".length));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (arg === "--host") {
|
|
59
|
+
const value = args[index + 1];
|
|
60
|
+
if (!value)
|
|
61
|
+
throw new Error("--host requires a value");
|
|
62
|
+
host = value;
|
|
63
|
+
index += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (arg.startsWith("--host=")) {
|
|
67
|
+
host = arg.slice("--host=".length);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (arg === "--allow-remote") {
|
|
71
|
+
allowRemote = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
75
|
+
}
|
|
76
|
+
if (port === undefined) {
|
|
77
|
+
return { transport: "stdio" };
|
|
78
|
+
}
|
|
79
|
+
if (!isLoopbackHost(host) && !allowRemote) {
|
|
80
|
+
throw new Error(`Refusing to bind SSE transport on non-loopback host '${host}' without --allow-remote (or CDP_MCP_ALLOW_REMOTE=1). MCP tools include in-page eval and server-filesystem writes; remote exposure is opt-in.`);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
transport: "sse",
|
|
84
|
+
host,
|
|
85
|
+
port,
|
|
86
|
+
allowRemote,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function parsePort(raw) {
|
|
90
|
+
const port = Number(raw);
|
|
91
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
92
|
+
throw new Error(`Invalid --port value: ${raw}`);
|
|
93
|
+
}
|
|
94
|
+
return port;
|
|
95
|
+
}
|
|
96
|
+
async function main() {
|
|
97
|
+
const mode = parseArgs(process.argv.slice(2));
|
|
98
|
+
if (mode.transport === "sse") {
|
|
99
|
+
await runSseServer(mode);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
await runStdioServer();
|
|
103
|
+
}
|
|
104
|
+
async function runStdioServer() {
|
|
105
|
+
const server = buildServer();
|
|
106
|
+
const transport = new StdioServerTransport();
|
|
107
|
+
await server.connect(transport);
|
|
108
|
+
log.info("cdp-mcp server started", { pid: process.pid });
|
|
109
|
+
const shutdown = async (signal) => {
|
|
110
|
+
log.info(`shutdown signal: ${signal}`);
|
|
111
|
+
try {
|
|
112
|
+
const session = getSession();
|
|
113
|
+
if (session)
|
|
114
|
+
await sessionState.close();
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
log.warn("error during shutdown", { error: String(e) });
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await server.close();
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
log.warn("error closing server", { error: String(e) });
|
|
124
|
+
}
|
|
125
|
+
process.exit(0);
|
|
126
|
+
};
|
|
127
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
128
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
129
|
+
installProcessErrorHandlers();
|
|
130
|
+
}
|
|
131
|
+
async function runSseServer(mode) {
|
|
132
|
+
const clients = new Map();
|
|
133
|
+
// Host/Origin validation is a DNS-rebinding defense — it bites only
|
|
134
|
+
// on loopback binds, where an attacker page can be tricked into
|
|
135
|
+
// reaching 127.0.0.1 via a rebound DNS name. On non-loopback binds
|
|
136
|
+
// the operator has explicitly accepted exposure via --allow-remote,
|
|
137
|
+
// and we cannot statically enumerate every hostname/IP the host
|
|
138
|
+
// might be reached by (LAN IP, hostname, mDNS, VPN, …) — so we skip
|
|
139
|
+
// both checks and treat --allow-remote as the gate.
|
|
140
|
+
const validateHostOrigin = isLoopbackHost(mode.host);
|
|
141
|
+
const allowedHosts = validateHostOrigin ? buildAllowedHosts(mode.host, mode.port) : new Set();
|
|
142
|
+
const allowedOrigins = validateHostOrigin ? buildAllowedOrigins(mode.host, mode.port) : new Set();
|
|
143
|
+
const httpServer = createServer((req, res) => {
|
|
144
|
+
void handleSseRequest({
|
|
145
|
+
req,
|
|
146
|
+
res,
|
|
147
|
+
clients,
|
|
148
|
+
host: mode.host,
|
|
149
|
+
port: mode.port,
|
|
150
|
+
validateHostOrigin,
|
|
151
|
+
allowedHosts,
|
|
152
|
+
allowedOrigins,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
await listen(httpServer, mode);
|
|
156
|
+
log.info("cdp-mcp SSE server started", {
|
|
157
|
+
pid: process.pid,
|
|
158
|
+
url: `http://${mode.host}:${mode.port}/sse`,
|
|
159
|
+
allowRemote: mode.allowRemote,
|
|
160
|
+
});
|
|
161
|
+
if (!isLoopbackHost(mode.host)) {
|
|
162
|
+
log.warn("SSE bound to non-loopback host — exposing in-page eval + filesystem-write tools without auth (operator opted in via --allow-remote)", { host: mode.host, port: mode.port });
|
|
163
|
+
}
|
|
164
|
+
// Close SSE transports BEFORE the HTTP server: Node's server.close()
|
|
165
|
+
// waits for in-flight requests to drain, and /sse connections are
|
|
166
|
+
// long-lived by design — closing the HTTP server first would hang
|
|
167
|
+
// SIGINT / SIGTERM indefinitely.
|
|
168
|
+
const shutdown = async (signal) => {
|
|
169
|
+
log.info(`shutdown signal: ${signal}`);
|
|
170
|
+
await closeSseClients(clients);
|
|
171
|
+
await closeHttpServer(httpServer);
|
|
172
|
+
try {
|
|
173
|
+
const session = getSession();
|
|
174
|
+
if (session)
|
|
175
|
+
await sessionState.close();
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
log.warn("error during shutdown", { error: String(e) });
|
|
179
|
+
}
|
|
180
|
+
process.exit(0);
|
|
181
|
+
};
|
|
182
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
183
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
184
|
+
installProcessErrorHandlers();
|
|
185
|
+
}
|
|
186
|
+
function installProcessErrorHandlers() {
|
|
187
|
+
process.on("uncaughtException", (err) => {
|
|
188
|
+
log.error("uncaughtException", { error: String(err), stack: err.stack });
|
|
189
|
+
});
|
|
190
|
+
process.on("unhandledRejection", (reason) => {
|
|
191
|
+
log.error("unhandledRejection", { reason: String(reason) });
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function handleSseRequest({ req, res, clients, host, port, validateHostOrigin, allowedHosts, allowedOrigins, }) {
|
|
195
|
+
try {
|
|
196
|
+
// DNS-rebinding defense for loopback binds: validate Host (and
|
|
197
|
+
// Origin if present) before the SDK touches the request. The MCP
|
|
198
|
+
// SDK's SSEServerTransport does not gate either header by default.
|
|
199
|
+
// Skipped on non-loopback binds — see runSseServer for rationale.
|
|
200
|
+
if (validateHostOrigin) {
|
|
201
|
+
const headerHost = req.headers.host;
|
|
202
|
+
if (!headerHost || !allowedHosts.has(headerHost)) {
|
|
203
|
+
log.warn("rejecting SSE request with disallowed Host header", {
|
|
204
|
+
host: headerHost,
|
|
205
|
+
bindHost: host,
|
|
206
|
+
});
|
|
207
|
+
respondText(res, 403, "forbidden\n");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// Origin is only sent by browsers (and is `null` for sandboxed
|
|
211
|
+
// iframes / file://). Checking it when present is the
|
|
212
|
+
// cross-origin defense layer; the Host check above is what
|
|
213
|
+
// catches non-browser callers and Origin-omitting browsers.
|
|
214
|
+
const originHeader = req.headers.origin;
|
|
215
|
+
if (originHeader && !allowedOrigins.has(originHeader)) {
|
|
216
|
+
log.warn("rejecting SSE request with disallowed Origin header", { origin: originHeader });
|
|
217
|
+
respondText(res, 403, "forbidden\n");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? `${host}:${port}`}`);
|
|
222
|
+
if (req.method === "GET" && url.pathname === "/sse") {
|
|
223
|
+
await startSseConnection({ res, clients });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (req.method === "POST" && url.pathname === "/messages") {
|
|
227
|
+
await handleSseMessage({ req, res, clients, sessionId: url.searchParams.get("sessionId") });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
respondText(res, 404, "not found\n");
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
log.error("SSE request failed", { error: String(e) });
|
|
234
|
+
if (!res.headersSent) {
|
|
235
|
+
respondText(res, 500, "internal server error\n");
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
res.end();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Called only on loopback binds (see runSseServer). The set covers the
|
|
243
|
+
// three loopback aliases a browser or CLI might use to reach a 127.0.0.1
|
|
244
|
+
// / localhost / ::1 server; anything else implies DNS-rebinding.
|
|
245
|
+
function buildAllowedHosts(bindHost, port) {
|
|
246
|
+
const hosts = new Set([
|
|
247
|
+
`${bindHost}:${port}`,
|
|
248
|
+
`127.0.0.1:${port}`,
|
|
249
|
+
`localhost:${port}`,
|
|
250
|
+
`[::1]:${port}`,
|
|
251
|
+
]);
|
|
252
|
+
return hosts;
|
|
253
|
+
}
|
|
254
|
+
function buildAllowedOrigins(bindHost, port) {
|
|
255
|
+
const origins = new Set();
|
|
256
|
+
for (const hostPort of buildAllowedHosts(bindHost, port)) {
|
|
257
|
+
origins.add(`http://${hostPort}`);
|
|
258
|
+
origins.add(`https://${hostPort}`);
|
|
259
|
+
}
|
|
260
|
+
return origins;
|
|
261
|
+
}
|
|
262
|
+
async function startSseConnection({ res, clients, }) {
|
|
263
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
264
|
+
const server = buildServer();
|
|
265
|
+
transport.onclose = () => {
|
|
266
|
+
const client = clients.get(transport.sessionId);
|
|
267
|
+
clients.delete(transport.sessionId);
|
|
268
|
+
void client?.server.close().catch((e) => {
|
|
269
|
+
log.warn("error closing MCP server for SSE client", { sessionId: transport.sessionId, error: String(e) });
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
transport.onerror = (e) => {
|
|
273
|
+
log.warn("SSE transport error", { sessionId: transport.sessionId, error: String(e) });
|
|
274
|
+
};
|
|
275
|
+
clients.set(transport.sessionId, { server, transport });
|
|
276
|
+
await server.connect(transport);
|
|
277
|
+
log.info("SSE client connected", { sessionId: transport.sessionId });
|
|
278
|
+
}
|
|
279
|
+
async function handleSseMessage({ req, res, clients, sessionId, }) {
|
|
280
|
+
if (!sessionId) {
|
|
281
|
+
respondText(res, 400, "missing sessionId\n");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const client = clients.get(sessionId);
|
|
285
|
+
if (!client) {
|
|
286
|
+
respondText(res, 404, "unknown sessionId\n");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
await client.transport.handlePostMessage(req, res);
|
|
290
|
+
}
|
|
291
|
+
async function listen(server, mode) {
|
|
292
|
+
await new Promise((resolve, reject) => {
|
|
293
|
+
const onError = (e) => {
|
|
294
|
+
server.off("listening", onListening);
|
|
295
|
+
reject(e);
|
|
296
|
+
};
|
|
297
|
+
const onListening = () => {
|
|
298
|
+
server.off("error", onError);
|
|
299
|
+
resolve();
|
|
300
|
+
};
|
|
301
|
+
server.once("error", onError);
|
|
302
|
+
server.once("listening", onListening);
|
|
303
|
+
server.listen(mode.port, mode.host);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
async function closeHttpServer(server) {
|
|
307
|
+
await new Promise((resolve, reject) => {
|
|
308
|
+
server.close((e) => {
|
|
309
|
+
if (e) {
|
|
310
|
+
reject(e);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
resolve();
|
|
314
|
+
});
|
|
315
|
+
}).catch((e) => {
|
|
316
|
+
log.warn("error closing HTTP server", { error: String(e) });
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async function closeSseClients(clients) {
|
|
320
|
+
const entries = [...clients.entries()];
|
|
321
|
+
clients.clear();
|
|
322
|
+
await Promise.all(entries.map(async ([sessionId, client]) => {
|
|
323
|
+
try {
|
|
324
|
+
await client.transport.close();
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
log.warn("error closing SSE transport", { sessionId, error: String(e) });
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
await client.server.close();
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
log.warn("error closing MCP server", { sessionId, error: String(e) });
|
|
334
|
+
}
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
function respondText(res, statusCode, body) {
|
|
338
|
+
res.writeHead(statusCode, { "Content-Type": "text/plain; charset=utf-8" });
|
|
339
|
+
res.end(body);
|
|
340
|
+
}
|
|
341
|
+
main().catch((err) => {
|
|
342
|
+
log.error("fatal", { error: String(err), stack: err?.stack });
|
|
343
|
+
process.exit(1);
|
|
344
|
+
});
|
|
345
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerSessionTools } from "./tools/session.js";
|
|
3
|
+
import { registerNavTools } from "./tools/nav.js";
|
|
4
|
+
import { registerSourceTools } from "./tools/source.js";
|
|
5
|
+
import { registerBreakpointTools } from "./tools/breakpoints.js";
|
|
6
|
+
import { registerExecutionTools } from "./tools/execution.js";
|
|
7
|
+
import { registerInspectTools } from "./tools/inspect.js";
|
|
8
|
+
import { registerConsoleTools } from "./tools/console.js";
|
|
9
|
+
import { registerNetworkTools } from "./tools/network.js";
|
|
10
|
+
import { registerDomTools } from "./tools/dom.js";
|
|
11
|
+
export function buildServer() {
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: "cdp-mcp",
|
|
14
|
+
version: "0.1.0",
|
|
15
|
+
});
|
|
16
|
+
registerSessionTools(server);
|
|
17
|
+
registerNavTools(server);
|
|
18
|
+
registerSourceTools(server);
|
|
19
|
+
registerBreakpointTools(server);
|
|
20
|
+
registerExecutionTools(server);
|
|
21
|
+
registerInspectTools(server);
|
|
22
|
+
registerConsoleTools(server);
|
|
23
|
+
registerNetworkTools(server);
|
|
24
|
+
registerDomTools(server);
|
|
25
|
+
return server;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +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,OAAO,MAAM,CAAC;AAChB,CAAC"}
|