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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -0
  3. package/dist/index.js +345 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/server.js +27 -0
  6. package/dist/server.js.map +1 -0
  7. package/dist/session/browser.js +394 -0
  8. package/dist/session/browser.js.map +1 -0
  9. package/dist/session/buffers.js +43 -0
  10. package/dist/session/buffers.js.map +1 -0
  11. package/dist/session/pause.js +99 -0
  12. package/dist/session/pause.js.map +1 -0
  13. package/dist/session/state.js +93 -0
  14. package/dist/session/state.js.map +1 -0
  15. package/dist/sourcemap/loader.js +138 -0
  16. package/dist/sourcemap/loader.js.map +1 -0
  17. package/dist/sourcemap/normalize.js +59 -0
  18. package/dist/sourcemap/normalize.js.map +1 -0
  19. package/dist/sourcemap/store.js +185 -0
  20. package/dist/sourcemap/store.js.map +1 -0
  21. package/dist/tools/_register.js +30 -0
  22. package/dist/tools/_register.js.map +1 -0
  23. package/dist/tools/breakpoints.js +164 -0
  24. package/dist/tools/breakpoints.js.map +1 -0
  25. package/dist/tools/console.js +48 -0
  26. package/dist/tools/console.js.map +1 -0
  27. package/dist/tools/dom.js +527 -0
  28. package/dist/tools/dom.js.map +1 -0
  29. package/dist/tools/execution.js +89 -0
  30. package/dist/tools/execution.js.map +1 -0
  31. package/dist/tools/inspect.js +178 -0
  32. package/dist/tools/inspect.js.map +1 -0
  33. package/dist/tools/nav.js +136 -0
  34. package/dist/tools/nav.js.map +1 -0
  35. package/dist/tools/network.js +137 -0
  36. package/dist/tools/network.js.map +1 -0
  37. package/dist/tools/session.js +76 -0
  38. package/dist/tools/session.js.map +1 -0
  39. package/dist/tools/source.js +63 -0
  40. package/dist/tools/source.js.map +1 -0
  41. package/dist/util/browser-resolve.js +263 -0
  42. package/dist/util/browser-resolve.js.map +1 -0
  43. package/dist/util/errors.js +12 -0
  44. package/dist/util/errors.js.map +1 -0
  45. package/dist/util/format.js +65 -0
  46. package/dist/util/format.js.map +1 -0
  47. package/dist/util/log.js +34 -0
  48. package/dist/util/log.js.map +1 -0
  49. package/package.json +74 -0
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ import CDP from "chrome-remote-interface";
3
+ import { launchChrome, attachChrome, closeSession, switchTarget } from "../session/browser.js";
4
+ import { getSession, requireSession } from "../session/state.js";
5
+ import { registerJsonTool } from "./_register.js";
6
+ export function registerSessionTools(server) {
7
+ registerJsonTool(server, "launch_chrome", "Launch a new Chrome instance with remote debugging and attach. Returns the active target.", {
8
+ url: z.string().optional().describe("Initial URL to load (default: about:blank)"),
9
+ headless: z.boolean().optional().describe("Run headless"),
10
+ user_data_dir: z.string().optional().describe("Profile dir"),
11
+ args: z.array(z.string()).optional().describe("Extra chrome flags"),
12
+ chrome_path: z
13
+ .string()
14
+ .optional()
15
+ .describe("Optional explicit path to a Chrome/Chromium binary. Omit by default — chrome-launcher auto-detects (on Linux it searches PATH for google-chrome-stable, google-chrome, chromium-browser, chromium). Pass this only if a previous call failed because chrome-launcher could not find a binary."),
16
+ sandbox: z
17
+ .boolean()
18
+ .optional()
19
+ .describe("Enable Chromium's sandbox. Default false — we add --no-sandbox. On Ubuntu 23.10+ AppArmor restricts the unprivileged user namespace Chromium's sandbox depends on, so unsandboxed launch is the working default for automation. Pass true only on a host with a working sandbox path (AppArmor userns allowance or SUID chrome_sandbox helper)."),
20
+ }, async (input) => {
21
+ return await launchChrome({
22
+ url: input.url,
23
+ headless: input.headless,
24
+ userDataDir: input.user_data_dir,
25
+ args: input.args,
26
+ chromePath: input.chrome_path,
27
+ sandbox: input.sandbox,
28
+ });
29
+ });
30
+ registerJsonTool(server, "attach_chrome", "Attach to an already-running Chrome started with --remote-debugging-port. Picks the first matching page.", {
31
+ port: z.number().int().positive().optional().describe("Default 9222"),
32
+ host: z.string().optional(),
33
+ target_filter: z
34
+ .object({
35
+ type: z.string().optional(),
36
+ url_includes: z.string().optional(),
37
+ })
38
+ .optional(),
39
+ }, async (input) => {
40
+ return await attachChrome({
41
+ port: input.port,
42
+ host: input.host,
43
+ targetFilter: input.target_filter
44
+ ? {
45
+ ...(input.target_filter.type ? { type: input.target_filter.type } : {}),
46
+ ...(input.target_filter.url_includes ? { urlIncludes: input.target_filter.url_includes } : {}),
47
+ }
48
+ : undefined,
49
+ });
50
+ });
51
+ registerJsonTool(server, "close_session", "Close the active CDP session. Kills the browser if we launched it; leaves it alone if we attached.", undefined, async () => {
52
+ if (!getSession())
53
+ return "no active session";
54
+ await closeSession();
55
+ return "closed";
56
+ });
57
+ registerJsonTool(server, "list_targets", "List all debuggable targets on the current browser (pages, workers, iframes).", undefined, async () => {
58
+ const s = requireSession();
59
+ const targets = await CDP.List({ port: s.chromePort });
60
+ return targets.map((t) => ({
61
+ id: t.id,
62
+ type: t.type,
63
+ url: t.url,
64
+ title: t.title,
65
+ active: t.id === s.currentTargetId,
66
+ }));
67
+ });
68
+ registerJsonTool(server, "select_target", "Switch the active page target. Closes the current CDP socket and opens a new one.", { id: z.string().describe("Target ID from list_targets") }, async (input) => {
69
+ const s = requireSession();
70
+ if (s.currentTargetId === input.id)
71
+ return { id: input.id, status: "already-active" };
72
+ const r = await switchTarget(input.id);
73
+ return { id: r.targetId, url: r.url, status: "switched" };
74
+ });
75
+ }
76
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,GAAG,MAAM,yBAAyB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,gBAAgB,CACd,MAAM,EACN,eAAe,EACf,2FAA2F,EAC3F;QACE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QACjF,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;QACzD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC5D,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QACnE,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,+RAA+R,CAChS;QACH,OAAO,EAAE,CAAC;aACP,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CACP,iVAAiV,CAClV;KACJ,EACD,KAAK,EAAE,KAON,EAAE,EAAE;QACH,OAAO,MAAM,YAAY,CAAC;YACxB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,WAAW,EAAE,KAAK,CAAC,aAAa;YAChC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,WAAW;YAC7B,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IAEF,gBAAgB,CACd,MAAM,EACN,eAAe,EACf,0GAA0G,EAC1G;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;QACrE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC3B,aAAa,EAAE,CAAC;aACb,MAAM,CAAC;YACN,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC3B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACpC,CAAC;aACD,QAAQ,EAAE;KACd,EACD,KAAK,EAAE,KAAiG,EAAE,EAAE;QAC1G,OAAO,MAAM,YAAY,CAAC;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,KAAK,CAAC,aAAa;gBAC/B,CAAC,CAAC;oBACE,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvE,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC/F;gBACH,CAAC,CAAC,SAAS;SACd,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IAEF,gBAAgB,CACd,MAAM,EACN,eAAe,EACf,oGAAoG,EACpG,SAAS,EACT,KAAK,IAAI,EAAE;QACT,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,mBAAmB,CAAC;QAC9C,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO,QAAQ,CAAC;IAClB,CAAC,CACF,CAAC;IAEF,gBAAgB,CACd,MAAM,EACN,cAAc,EACd,+EAA+E,EAC/E,SAAS,EACT,KAAK,IAAI,EAAE;QACT,MAAM,CAAC,GAAG,cAAc,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,UAAW,EAAE,CAAC,CAAC;QACxD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzB,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,eAAe;SACnC,CAAC,CAAC,CAAC;IACN,CAAC,CACF,CAAC;IAEF,gBAAgB,CACd,MAAM,EACN,eAAe,EACf,mFAAmF,EACnF,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC,EAAE,EAC1D,KAAK,EAAE,KAAqB,EAAE,EAAE;QAC9B,MAAM,CAAC,GAAG,cAAc,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,eAAe,KAAK,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QACtF,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC5D,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { requireSession } from "../session/state.js";
3
+ import { mapOriginalToGenerated } from "../sourcemap/store.js";
4
+ import { registerJsonTool } from "./_register.js";
5
+ export function registerSourceTools(server) {
6
+ registerJsonTool(server, "list_scripts", "List scripts the browser has parsed in the active page, with source-map status.", {
7
+ mapped_only: z.boolean().optional().describe("Default true: only return scripts whose map loaded"),
8
+ url_includes: z.string().optional(),
9
+ limit: z.number().int().positive().optional(),
10
+ }, async (input) => {
11
+ const s = requireSession();
12
+ const mappedOnly = input.mapped_only ?? true;
13
+ let scripts = s.scripts.all().filter((sc) => !!sc.url);
14
+ if (mappedOnly)
15
+ scripts = scripts.filter((sc) => !!sc.consumer);
16
+ if (input.url_includes)
17
+ scripts = scripts.filter((sc) => sc.url.includes(input.url_includes));
18
+ if (input.limit)
19
+ scripts = scripts.slice(0, input.limit);
20
+ return scripts.map((sc) => ({
21
+ script_id: sc.scriptId,
22
+ // session_id disambiguates root vs worker/iframe entries with
23
+ // colliding scriptIds (CDP scriptIds are per-Debugger-agent). null
24
+ // = root; survives JSON serialization, unlike undefined.
25
+ session_id: sc.sessionId ?? null,
26
+ url: sc.url,
27
+ source_map_url: sc.sourceMapURL,
28
+ has_map: !!sc.consumer,
29
+ load_error: sc.loadError,
30
+ original_sources: sc.sources?.slice(0, 30),
31
+ original_source_count: sc.sources?.length ?? 0,
32
+ }));
33
+ });
34
+ registerJsonTool(server, "get_script_source", "Fetch the raw generated (JS) source text for a script by CDP script ID. Pass `session_id` from list_scripts to fetch a worker/iframe script — CDP scriptIds are per-Debugger-agent, so omitting session_id always routes to root.", {
35
+ script_id: z.string(),
36
+ session_id: z.string().nullable().optional().describe("From list_scripts. null or omitted = root."),
37
+ }, async (input) => {
38
+ const s = requireSession();
39
+ // null is the explicit "root" sentinel from the projection; CDP wants undefined.
40
+ const sid = input.session_id ?? undefined;
41
+ const result = await s.client.send("Debugger.getScriptSource", { scriptId: input.script_id }, sid);
42
+ return { script_id: input.script_id, session_id: sid ?? null, source: result.scriptSource };
43
+ });
44
+ registerJsonTool(server, "resolve_source_position", "Translate a TS source coord into the JS (generated) coords CDP would use. Useful for diagnosing why a breakpoint didn't bind.", {
45
+ file: z.string(),
46
+ line: z.number().int().positive().describe("1-based"),
47
+ column: z.number().int().nonnegative().optional(),
48
+ }, async (input) => {
49
+ const s = requireSession();
50
+ const candidates = mapOriginalToGenerated(s.scripts, input.file, input.line, input.column ?? 0);
51
+ return {
52
+ query: { file: input.file, line: input.line, column: input.column ?? 0 },
53
+ candidates: candidates.map((c) => ({
54
+ script_id: c.scriptId,
55
+ session_id: c.sessionId ?? null,
56
+ script_url: c.scriptUrl,
57
+ line: c.lineNumber + 1, // public is 1-based
58
+ column: c.columnNumber,
59
+ })),
60
+ };
61
+ });
62
+ }
63
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/tools/source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,263 @@
1
+ // Shared Chromium/Chrome resolver used by BOTH the L3 e2e suite
2
+ // (test/e2e/setup/browser-path.ts re-exports from here) AND the L4 eval
3
+ // harness (evals/harness/runner.ts imports resolveBrowser() and feeds
4
+ // the result into the spawned MCP server via CHROME_PATH). Single
5
+ // resolution path so both layers cannot test against different
6
+ // protocol versions — the intent already documented in
7
+ // docs/test-eval-plan.md §L3 *Production code change required for
8
+ // chromePath* and §L3 *CI matrix* ("the same helper is reused by the
9
+ // eval harness").
10
+ //
11
+ // Resolution order (fail-fast — never silently fall back to chrome-
12
+ // launcher's auto-detection, because that's how the wrong browser ends
13
+ // up running and tests pass against the wrong protocol revision):
14
+ // 1. CDP_TEST_BROWSER_PATH env — explicit override, used by CI after
15
+ // `npx playwright install --with-deps chromium`. Wins everything.
16
+ // 2. `which chromium` (Linux/macOS) / `where chromium.exe` (Windows) —
17
+ // local dev path. Catches both /snap/bin/chromium and the apt
18
+ // /usr/bin/chromium symlink.
19
+ // 3. Playwright bundled cache — ~/.cache/ms-playwright/chromium-*/
20
+ // chrome-linux/chrome (Linux), ~/Library/Caches/ms-playwright/...
21
+ // (macOS), %LOCALAPPDATA%\ms-playwright\... (Windows). Handy after
22
+ // a fresh `npx playwright install`.
23
+ // 4. CDP_TEST_BROWSER=chrome — chrome-launcher's default detection.
24
+ // 5. Fail with an actionable install hint.
25
+ import { execSync } from "node:child_process";
26
+ import { existsSync, readdirSync, realpathSync, statSync } from "node:fs";
27
+ import { homedir, platform } from "node:os";
28
+ import { join } from "node:path";
29
+ export function getBrowserChoice() {
30
+ const env = process.env.CDP_TEST_BROWSER?.toLowerCase();
31
+ if (env === "chrome")
32
+ return "chrome";
33
+ if (env === "chromium" || env === undefined || env === "")
34
+ return "chromium";
35
+ throw new Error(`CDP_TEST_BROWSER must be 'chromium' or 'chrome' (got '${env}'). Default is 'chromium'.`);
36
+ }
37
+ export function resolveBrowser(choice = getBrowserChoice()) {
38
+ // Step 1 — explicit override wins.
39
+ const override = process.env.CDP_TEST_BROWSER_PATH;
40
+ if (override) {
41
+ if (!existsSync(override)) {
42
+ throw new Error(`CDP_TEST_BROWSER_PATH='${override}' does not exist. Unset it or install the binary.`);
43
+ }
44
+ return {
45
+ binaryPath: override,
46
+ choice,
47
+ snapConfined: override.startsWith("/snap/"),
48
+ source: "CDP_TEST_BROWSER_PATH",
49
+ };
50
+ }
51
+ // Step 2 — system-installed chromium (Linux/macOS via `which`, Windows via
52
+ // `where`). Skip when choice is 'chrome' — Chrome detection is delegated to
53
+ // chrome-launcher's defaults in step 4.
54
+ //
55
+ // On darwin we additionally skip the deprecated Homebrew cask `chromium`
56
+ // wrapper at /opt/homebrew/bin/chromium (and any path whose realpath
57
+ // resolves into /opt/homebrew/Caskroom/chromium/). That wrapper points
58
+ // at an unsigned .app Gatekeeper rejects as "damaged" — it launches
59
+ // successfully but the debug port never opens, so chrome-launcher's
60
+ // startup-port poll ECONNREFUSEs. The same gap is precisely what the
61
+ // §macOS arm64 entry in docs/known-chromium-gaps.md documents; skipping
62
+ // it here is what makes that entry's "resolveBrowser picks it up
63
+ // automatically" claim actually true for users who tried the cask first
64
+ // before reading the doc. Cask deprecation removal is scheduled
65
+ // 2026-09-01; once removed this skip becomes a no-op.
66
+ if (choice === "chromium") {
67
+ const sys = findOnPath("chromium") ?? findOnPath("chromium-browser");
68
+ if (sys && !isBrewCaskChromium(sys)) {
69
+ return {
70
+ binaryPath: sys,
71
+ choice,
72
+ snapConfined: sys.startsWith("/snap/"),
73
+ source: "which-chromium",
74
+ };
75
+ }
76
+ }
77
+ // Step 3 — Playwright's bundled cache. Used in CI after
78
+ // `npx playwright install`; also useful locally when the system chromium
79
+ // isn't installed.
80
+ const pw = findPlaywrightChromium();
81
+ if (pw && choice === "chromium") {
82
+ return {
83
+ binaryPath: pw,
84
+ choice,
85
+ snapConfined: false,
86
+ source: "playwright-cache",
87
+ };
88
+ }
89
+ // Step 4 — Chrome stable, only when explicitly requested. We don't return a
90
+ // path here; the caller forwards `undefined` to chrome-launcher, which runs
91
+ // its own detection. This is intentionally NOT used as a silent fallback
92
+ // for chromium because the protocol revision can diverge.
93
+ if (choice === "chrome") {
94
+ // chrome-launcher uses its own which() — we just signal it should take
95
+ // over by returning the marker path "chrome-launcher-default". Both
96
+ // callers strip this back to undefined before forwarding the path: L3
97
+ // global setup at test/e2e/setup/global.ts skips `chromePath` entirely,
98
+ // and the L4 runner at evals/harness/runner.ts omits CHROME_PATH from
99
+ // the subprocess env via isChromeLauncherDefault().
100
+ return {
101
+ binaryPath: "chrome-launcher-default",
102
+ choice,
103
+ snapConfined: false,
104
+ source: "chrome-launcher-default",
105
+ };
106
+ }
107
+ // Step 5 — actionable failure.
108
+ const isArm = process.arch === "arm64";
109
+ const installHint = isArm
110
+ ? "sudo apt-get install chromium-browser # OR: npx playwright install chromium"
111
+ : "sudo snap install chromium OR sudo apt-get install chromium-browser OR npx playwright install chromium";
112
+ throw new Error(`Could not resolve a Chromium binary for the e2e suite.\n` +
113
+ `Tried (in order): CDP_TEST_BROWSER_PATH env, which chromium, Playwright cache.\n` +
114
+ `Install Chromium (${installHint}) or set CDP_TEST_BROWSER_PATH explicitly.`);
115
+ }
116
+ // Detect the deprecated Homebrew cask `chromium` wrapper on darwin. Returns
117
+ // true for both the well-known /opt/homebrew/bin/chromium path and any path
118
+ // whose realpath resolves into /opt/homebrew/Caskroom/chromium/ (covers
119
+ // future brew layout shifts where the wrapper lives elsewhere but the
120
+ // Caskroom prefix stays put). Never true off darwin.
121
+ function isBrewCaskChromium(binaryPath) {
122
+ if (platform() !== "darwin")
123
+ return false;
124
+ if (binaryPath === "/opt/homebrew/bin/chromium")
125
+ return true;
126
+ try {
127
+ const resolved = realpathSync(binaryPath);
128
+ return resolved.startsWith("/opt/homebrew/Caskroom/chromium/");
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ }
134
+ function findOnPath(cmd) {
135
+ try {
136
+ const which = platform() === "win32" ? "where" : "which";
137
+ const out = execSync(`${which} ${cmd}`, { stdio: ["ignore", "pipe", "ignore"] })
138
+ .toString()
139
+ .split(/\r?\n/)
140
+ .find((line) => line.trim().length > 0);
141
+ if (!out)
142
+ return null;
143
+ const path = out.trim();
144
+ return existsSync(path) ? path : null;
145
+ }
146
+ catch {
147
+ return null;
148
+ }
149
+ }
150
+ function findPlaywrightChromium() {
151
+ const candidates = playwrightCacheDirs();
152
+ for (const dir of candidates) {
153
+ if (!existsSync(dir))
154
+ continue;
155
+ let entries;
156
+ try {
157
+ entries = readdirSync(dir);
158
+ }
159
+ catch {
160
+ continue;
161
+ }
162
+ // Filter to chromium-* / chromium-headless-shell-* — pick the highest-
163
+ // numbered revision (newest install).
164
+ const chromiums = entries
165
+ .filter((e) => e.startsWith("chromium-") && !e.startsWith("chromium-headless-shell-"))
166
+ .map((e) => ({ name: e, full: join(dir, e) }))
167
+ .filter((e) => {
168
+ try {
169
+ return statSync(e.full).isDirectory();
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ })
175
+ .sort((a, b) => b.name.localeCompare(a.name));
176
+ for (const c of chromiums) {
177
+ const exe = pickPlaywrightExe(c.full);
178
+ if (exe)
179
+ return exe;
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+ function playwrightCacheDirs() {
185
+ const home = homedir();
186
+ const plat = platform();
187
+ if (plat === "linux")
188
+ return [join(home, ".cache", "ms-playwright")];
189
+ if (plat === "darwin")
190
+ return [join(home, "Library", "Caches", "ms-playwright")];
191
+ if (plat === "win32") {
192
+ const local = process.env.LOCALAPPDATA ?? join(home, "AppData", "Local");
193
+ return [join(local, "ms-playwright")];
194
+ }
195
+ return [join(home, ".cache", "ms-playwright")];
196
+ }
197
+ function pickPlaywrightExe(chromiumDir) {
198
+ // Layout, per Playwright's install layout:
199
+ // Linux: chromium-<rev>/chrome-linux/chrome
200
+ // macOS: chromium-<rev>/chrome-mac/Chromium.app/Contents/MacOS/Chromium
201
+ // Windows: chromium-<rev>/chrome-win/chrome.exe
202
+ const plat = platform();
203
+ // Playwright's layout varies by version/arch (Chrome-for-Testing renamed
204
+ // some folders ~Playwright 1.40+):
205
+ // Linux x86_64: chromium-<rev>/chrome-linux/chrome (older)
206
+ // chromium-<rev>/chrome-linux64/chrome (CfT, newer)
207
+ // Linux ARM64: chromium-<rev>/chrome-linux/chrome (ARM64 unchanged)
208
+ // chromium-<rev>/chrome-linux-arm64/chrome (rare variant)
209
+ // macOS x86_64: chromium-<rev>/chrome-mac/Chromium.app/... (older)
210
+ // chromium-<rev>/chrome-mac-x64/Google Chrome for Testing.app/... (CfT, newer)
211
+ // macOS arm64: chromium-<rev>/chrome-mac-arm/Chromium.app/... (older)
212
+ // chromium-<rev>/chrome-mac-arm64/Google Chrome for Testing.app/...(CfT, newer)
213
+ // Windows: chromium-<rev>/chrome-win/chrome.exe (older)
214
+ // chromium-<rev>/chrome-win64/chrome.exe (newer)
215
+ // Codex blocker review on PR #11 flagged the missing chrome-linux64
216
+ // candidate — modern Playwright on ubuntu-latest landed in CfT layout
217
+ // and the resolver step exited 1 before the test runner started.
218
+ // The mac-arm64 CfT candidates were added while validating
219
+ // set_breakpoint idempotency on darwin-arm64, where Playwright
220
+ // v1223+ installs to chrome-mac-arm64/Google Chrome
221
+ // for Testing.app/Contents/MacOS/Google Chrome for Testing.
222
+ const candidates = plat === "win32"
223
+ ? [
224
+ join(chromiumDir, "chrome-win64", "chrome.exe"),
225
+ join(chromiumDir, "chrome-win", "chrome.exe"),
226
+ ]
227
+ : plat === "darwin"
228
+ ? [
229
+ // CfT-renamed layouts first (Playwright 1.40+ / chromium revisions
230
+ // ~v1200+). `existsSync` makes the order forgiving — try newer
231
+ // first so we don't accidentally pick an older sibling layout
232
+ // when both happen to be present.
233
+ join(chromiumDir, "chrome-mac-arm64", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"),
234
+ join(chromiumDir, "chrome-mac-x64", "Google Chrome for Testing.app", "Contents", "MacOS", "Google Chrome for Testing"),
235
+ // Older Chromium.app-branded layouts.
236
+ join(chromiumDir, "chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium"),
237
+ join(chromiumDir, "chrome-mac-arm", "Chromium.app", "Contents", "MacOS", "Chromium"),
238
+ ]
239
+ : [
240
+ join(chromiumDir, "chrome-linux", "chrome"),
241
+ join(chromiumDir, "chrome-linux64", "chrome"),
242
+ join(chromiumDir, "chrome-linux-arm64", "chrome"),
243
+ ];
244
+ for (const c of candidates) {
245
+ if (existsSync(c))
246
+ return c;
247
+ }
248
+ return null;
249
+ }
250
+ /** True when the given binary is the snap-marker returned by step 4. */
251
+ export function isChromeLauncherDefault(b) {
252
+ return b.source === "chrome-launcher-default";
253
+ }
254
+ /** Determine the user-data-dir for snap-confined Chromium. Snap confinement
255
+ * rejects /tmp/... paths; only ~/snap/<app>/current/ is writable. */
256
+ export function snapUserDataDir(binaryPath) {
257
+ // Parse the snap app name out of the binary path. /snap/bin/chromium ->
258
+ // 'chromium'; /snap/firefox/current/firefox -> 'firefox'.
259
+ const match = binaryPath.match(/\/snap\/(?:bin\/)?([^/]+)/);
260
+ const app = match?.[1] ?? "chromium";
261
+ return join(homedir(), "snap", app, "current", "cdp-mcp-test-profile");
262
+ }
263
+ //# sourceMappingURL=browser-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-resolve.js","sourceRoot":"","sources":["../../src/util/browser-resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export class ToolError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "ToolError";
7
+ }
8
+ }
9
+ export const noSession = () => new ToolError("no_session", "No browser session. Call launch_chrome or attach_chrome first.");
10
+ export const notPaused = () => new ToolError("not_paused", "Operation requires the debugger to be paused. Set a breakpoint and call wait_for_pause.");
11
+ export const alreadySession = () => new ToolError("already_session", "A session is already active. Call close_session before opening a new one.");
12
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/util/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAAU,SAAQ,KAAK;IACf;IAAnB,YAAmB,IAAY,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QADE,SAAI,GAAJ,IAAI,CAAQ;QAE7B,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAC5B,IAAI,SAAS,CACX,YAAY,EACZ,gEAAgE,CACjE,CAAC;AAEJ,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAC5B,IAAI,SAAS,CACX,YAAY,EACZ,yFAAyF,CAC1F,CAAC;AAEJ,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,EAAE,CACjC,IAAI,SAAS,CACX,iBAAiB,EACjB,2EAA2E,CAC5E,CAAC"}
@@ -0,0 +1,65 @@
1
+ const MAX_PREVIEW = 200;
2
+ export function truncate(s, max = MAX_PREVIEW) {
3
+ if (s.length <= max)
4
+ return s;
5
+ return s.slice(0, max) + `…(+${s.length - max} chars)`;
6
+ }
7
+ // Compact textual preview of a CDP RemoteObject without dereferencing further.
8
+ export function previewRemoteObject(obj) {
9
+ if (obj.unserializableValue)
10
+ return obj.unserializableValue;
11
+ if (obj.type === "undefined")
12
+ return "undefined";
13
+ if (obj.subtype === "null")
14
+ return "null";
15
+ if (obj.type === "string")
16
+ return JSON.stringify(obj.value ?? "");
17
+ if (obj.type === "number" || obj.type === "boolean" || obj.type === "bigint") {
18
+ return String(obj.value);
19
+ }
20
+ if (obj.type === "function") {
21
+ return obj.description ? truncate(obj.description) : "function";
22
+ }
23
+ if (obj.preview) {
24
+ return truncate(previewFromPreview(obj.preview));
25
+ }
26
+ if (obj.description)
27
+ return truncate(obj.description);
28
+ return obj.className ?? obj.subtype ?? obj.type;
29
+ }
30
+ function previewFromPreview(p) {
31
+ if (p.subtype === "array") {
32
+ const items = (p.properties ?? [])
33
+ .map((pp) => previewProperty(pp))
34
+ .join(", ");
35
+ return `[${items}${p.overflow ? ", …" : ""}]`;
36
+ }
37
+ const items = (p.properties ?? [])
38
+ .map((pp) => `${pp.name}: ${previewProperty(pp)}`)
39
+ .join(", ");
40
+ const head = p.description && p.description !== "Object" ? `${p.description} ` : "";
41
+ return `${head}{${items}${p.overflow ? ", …" : ""}}`;
42
+ }
43
+ function previewProperty(p) {
44
+ if (p.type === "string")
45
+ return JSON.stringify(p.value ?? "");
46
+ if (p.value !== undefined)
47
+ return p.value;
48
+ return p.type;
49
+ }
50
+ export function describeRemote(obj) {
51
+ return {
52
+ type: obj.subtype ?? obj.type,
53
+ preview: previewRemoteObject(obj),
54
+ ...(obj.objectId ? { objectId: obj.objectId } : {}),
55
+ };
56
+ }
57
+ // Wrap any value into the MCP tool content envelope.
58
+ export function toolText(text) {
59
+ return { content: [{ type: "text", text }] };
60
+ }
61
+ // Stringify a JSON result with a stable shape for the LLM to parse.
62
+ export function toolJson(value) {
63
+ return toolText(JSON.stringify(value, null, 2));
64
+ }
65
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/util/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ // Logging for an MCP stdio server.
2
+ //
3
+ // stdout is reserved for JSON-RPC framing — anything we write there will
4
+ // corrupt the protocol. All logging goes to stderr.
5
+ const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
6
+ function envLevel() {
7
+ const raw = process.env.CDP_MCP_LOG?.toLowerCase();
8
+ if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error")
9
+ return raw;
10
+ return "info";
11
+ }
12
+ const threshold = LEVELS[envLevel()];
13
+ function emit(level, msg, meta) {
14
+ if (LEVELS[level] < threshold)
15
+ return;
16
+ const ts = new Date().toISOString();
17
+ const tail = meta ? " " + safeJson(meta) : "";
18
+ process.stderr.write(`[${ts}] ${level.toUpperCase()} ${msg}${tail}\n`);
19
+ }
20
+ function safeJson(v) {
21
+ try {
22
+ return JSON.stringify(v);
23
+ }
24
+ catch {
25
+ return String(v);
26
+ }
27
+ }
28
+ export const log = {
29
+ debug: (msg, meta) => emit("debug", msg, meta),
30
+ info: (msg, meta) => emit("info", msg, meta),
31
+ warn: (msg, meta) => emit("warn", msg, meta),
32
+ error: (msg, meta) => emit("error", msg, meta),
33
+ };
34
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/util/log.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,yEAAyE;AACzE,oDAAoD;AAIpD,MAAM,MAAM,GAA0B,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAEnF,SAAS,QAAQ;IACf,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC;IACnD,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC;IACvF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;AAErC,SAAS,IAAI,CAAC,KAAY,EAAE,GAAW,EAAE,IAA8B;IACrE,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS;QAAE,OAAO;IACtC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAChF,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC9E,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC9E,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;CACjF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "cdp-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Chrome DevTools Protocol MCP server — a TypeScript-aware frontend debugger for AI agents.",
5
+ "license": "MIT",
6
+ "author": "Leonard Janke",
7
+ "homepage": "https://github.com/lcjanke2020/cdp-mcp#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/lcjanke2020/cdp-mcp.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/lcjanke2020/cdp-mcp/issues"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "chrome-devtools-protocol",
19
+ "cdp",
20
+ "debugger",
21
+ "devtools",
22
+ "ai-agents",
23
+ "typescript",
24
+ "source-maps"
25
+ ],
26
+ "type": "module",
27
+ "bin": {
28
+ "cdp-mcp": "dist/index.js"
29
+ },
30
+ "main": "dist/index.js",
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "dev": "tsx src/index.ts",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "pretest:e2e": "node scripts/check-chromium-skips.mjs && npm run sample:build",
40
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
41
+ "sample:build": "npm ci --prefix examples/sample-app && npm run --prefix examples/sample-app build && npm run variants:build",
42
+ "variants:build": "node scripts/build-variants.mjs",
43
+ "lint:chromium-skips": "node scripts/check-chromium-skips.mjs",
44
+ "smoke": "node scripts/smoke.mjs",
45
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.evals.json",
46
+ "preeval": "npm run build",
47
+ "preeval:quick": "npm run build",
48
+ "eval": "tsx evals/cli.ts",
49
+ "eval:quick": "tsx evals/cli.ts --scenarios=compute-step --trials=1",
50
+ "clean": "rimraf dist",
51
+ "prepack": "npm run build"
52
+ },
53
+ "dependencies": {
54
+ "@jridgewell/source-map": "^0.3.11",
55
+ "@modelcontextprotocol/sdk": "^1.29.0",
56
+ "chrome-launcher": "^1.2.1",
57
+ "chrome-remote-interface": "^0.34.0",
58
+ "devtools-protocol": "^0.0.1628107",
59
+ "zod": "^3.23.8"
60
+ },
61
+ "devDependencies": {
62
+ "@anthropic-ai/sdk": "^0.30.1",
63
+ "@google/genai": "^2.4.0",
64
+ "@types/chrome-remote-interface": "^0.33.0",
65
+ "@types/node": "^22.10.5",
66
+ "rimraf": "^6.0.1",
67
+ "tsx": "^4.19.2",
68
+ "typescript": "^5.7.2",
69
+ "vitest": "^2.1.8"
70
+ },
71
+ "engines": {
72
+ "node": ">=20"
73
+ }
74
+ }