cdp-mcp 0.2.1 → 0.4.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 (96) hide show
  1. package/README.md +28 -303
  2. package/bin.js +23 -0
  3. package/contract.d.ts +1 -0
  4. package/contract.js +2 -0
  5. package/index.d.ts +1 -0
  6. package/index.js +2 -0
  7. package/package.json +31 -73
  8. package/dist/contract.d.ts +0 -11
  9. package/dist/contract.js +0 -11
  10. package/dist/contract.js.map +0 -1
  11. package/dist/index.d.ts +0 -18
  12. package/dist/index.js +0 -402
  13. package/dist/index.js.map +0 -1
  14. package/dist/locator.d.ts +0 -108
  15. package/dist/locator.js +0 -176
  16. package/dist/locator.js.map +0 -1
  17. package/dist/server.d.ts +0 -2
  18. package/dist/server.js +0 -41
  19. package/dist/server.js.map +0 -1
  20. package/dist/session/browser.d.ts +0 -29
  21. package/dist/session/browser.js +0 -409
  22. package/dist/session/browser.js.map +0 -1
  23. package/dist/session/buffers.d.ts +0 -48
  24. package/dist/session/buffers.js +0 -43
  25. package/dist/session/buffers.js.map +0 -1
  26. package/dist/session/pause.d.ts +0 -21
  27. package/dist/session/pause.js +0 -99
  28. package/dist/session/pause.js.map +0 -1
  29. package/dist/session/state.d.ts +0 -53
  30. package/dist/session/state.js +0 -93
  31. package/dist/session/state.js.map +0 -1
  32. package/dist/sourcemap/loader.d.ts +0 -4
  33. package/dist/sourcemap/loader.js +0 -138
  34. package/dist/sourcemap/loader.js.map +0 -1
  35. package/dist/sourcemap/normalize.d.ts +0 -2
  36. package/dist/sourcemap/normalize.js +0 -59
  37. package/dist/sourcemap/normalize.js.map +0 -1
  38. package/dist/sourcemap/store.d.ts +0 -57
  39. package/dist/sourcemap/store.js +0 -185
  40. package/dist/sourcemap/store.js.map +0 -1
  41. package/dist/tools/_locator_runtime.d.ts +0 -31
  42. package/dist/tools/_locator_runtime.js +0 -243
  43. package/dist/tools/_locator_runtime.js.map +0 -1
  44. package/dist/tools/_register.d.ts +0 -2
  45. package/dist/tools/_register.js +0 -30
  46. package/dist/tools/_register.js.map +0 -1
  47. package/dist/tools/breakpoints.d.ts +0 -4
  48. package/dist/tools/breakpoints.js +0 -164
  49. package/dist/tools/breakpoints.js.map +0 -1
  50. package/dist/tools/console.d.ts +0 -2
  51. package/dist/tools/console.js +0 -48
  52. package/dist/tools/console.js.map +0 -1
  53. package/dist/tools/dom.d.ts +0 -2
  54. package/dist/tools/dom.js +0 -309
  55. package/dist/tools/dom.js.map +0 -1
  56. package/dist/tools/execution.d.ts +0 -29
  57. package/dist/tools/execution.js +0 -89
  58. package/dist/tools/execution.js.map +0 -1
  59. package/dist/tools/forms.d.ts +0 -8
  60. package/dist/tools/forms.js +0 -256
  61. package/dist/tools/forms.js.map +0 -1
  62. package/dist/tools/inspect.d.ts +0 -2
  63. package/dist/tools/inspect.js +0 -178
  64. package/dist/tools/inspect.js.map +0 -1
  65. package/dist/tools/nav.d.ts +0 -2
  66. package/dist/tools/nav.js +0 -136
  67. package/dist/tools/nav.js.map +0 -1
  68. package/dist/tools/network.d.ts +0 -2
  69. package/dist/tools/network.js +0 -137
  70. package/dist/tools/network.js.map +0 -1
  71. package/dist/tools/session.d.ts +0 -2
  72. package/dist/tools/session.js +0 -76
  73. package/dist/tools/session.js.map +0 -1
  74. package/dist/tools/source.d.ts +0 -2
  75. package/dist/tools/source.js +0 -63
  76. package/dist/tools/source.js.map +0 -1
  77. package/dist/tools/storage.d.ts +0 -2
  78. package/dist/tools/storage.js +0 -296
  79. package/dist/tools/storage.js.map +0 -1
  80. package/dist/util/browser-resolve.d.ts +0 -19
  81. package/dist/util/browser-resolve.js +0 -263
  82. package/dist/util/browser-resolve.js.map +0 -1
  83. package/dist/util/errors.d.ts +0 -7
  84. package/dist/util/errors.js +0 -12
  85. package/dist/util/errors.js.map +0 -1
  86. package/dist/util/format.d.ts +0 -20
  87. package/dist/util/format.js +0 -65
  88. package/dist/util/format.js.map +0 -1
  89. package/dist/util/log.d.ts +0 -6
  90. package/dist/util/log.js +0 -34
  91. package/dist/util/log.js.map +0 -1
  92. package/docs/chromium-sandboxing.md +0 -197
  93. package/docs/known-chromium-gaps.md +0 -138
  94. package/docs/launchd-service.md +0 -217
  95. package/docs/local-l3-e2e-setup.md +0 -199
  96. package/docs/systemd-service.md +0 -233
@@ -1,217 +0,0 @@
1
- # macOS: Run as a Persistent Service (launchd)
2
-
3
- Register `cdp-mcp` as a launchd user agent so it starts automatically on login
4
- and exposes the MCP SSE endpoint on `127.0.0.1:9719`.
5
-
6
- Persistent service mode is useful for MCP clients that support SSE because the
7
- `cdp-mcp` process and its browser/CDP session can survive MCP client restarts or
8
- reconnects. It does **not** persist state across service-process restarts.
9
-
10
- > Security note: the local SSE endpoint has no authentication. MCP tools include
11
- > in-page JavaScript evaluation and filesystem writes via screenshot paths. Only
12
- > run a persistent service on trusted single-user machines, and do not bind it to
13
- > non-loopback interfaces unless you understand the `--allow-remote` exposure.
14
-
15
- ## Contents
16
-
17
- - [1. Install the server](#1-install-the-server)
18
- - [2. Create the plist](#2-create-the-plist)
19
- - [3. Load and start](#3-load-and-start)
20
- - [4. Verify](#4-verify)
21
- - [5. Configure an MCP client](#5-configure-an-mcp-client)
22
- - [6. Logs](#6-logs)
23
- - [7. Stop / uninstall](#7-stop--uninstall)
24
- - [8. Upgrade](#8-upgrade)
25
- - [Troubleshooting](#troubleshooting)
26
-
27
- ## 1. Install the server
28
-
29
- Requires Node.js 20+ and a local Chrome/Chromium browser.
30
-
31
- ```bash
32
- npm install -g cdp-mcp
33
- ```
34
-
35
- Verify with `cdp-mcp --help`. The package ships prebuilt `dist/`, so there is no
36
- build step and no repo checkout needed.
37
-
38
- If `launch_chrome` cannot find Chrome/Chromium automatically, set `CHROME_PATH`
39
- in the plist generated below.
40
-
41
- ## 2. Create the plist
42
-
43
- Run this from any directory:
44
-
45
- ```bash
46
- # If you use fnm, nvm, or another Node version manager, set these variables to
47
- # stable paths before running this snippet. Example:
48
- # NODE_BIN="$HOME/.local/share/fnm/aliases/default/bin/node"
49
- # CDP_SCRIPT="$HOME/.local/share/fnm/aliases/default/bin/cdp-mcp"
50
- NODE_BIN="${NODE_BIN:-$(command -v node)}"
51
- CDP_SCRIPT="${CDP_SCRIPT:-$(command -v cdp-mcp)}"
52
- CHROME_PATH="${CHROME_PATH:-}"
53
-
54
- if [ -z "$NODE_BIN" ]; then
55
- echo "Error: node not found in PATH. Install Node 20+ first." >&2
56
- exit 1
57
- fi
58
- if [ -z "$CDP_SCRIPT" ]; then
59
- echo "Error: cdp-mcp not found. Run 'npm install -g cdp-mcp' first." >&2
60
- exit 1
61
- fi
62
-
63
- xml_escape() {
64
- printf '%s' "$1" \
65
- | sed \
66
- -e 's/&/\&/g' \
67
- -e 's/</\&lt;/g' \
68
- -e 's/>/\&gt;/g' \
69
- -e 's/"/\&quot;/g' \
70
- -e "s/'/\&apos;/g"
71
- }
72
-
73
- NODE_DIR="$(dirname "$NODE_BIN")"
74
- mkdir -p ~/Library/LaunchAgents ~/Library/Logs/cdp-mcp
75
- ESC_NODE=$(xml_escape "$NODE_BIN")
76
- ESC_NODE_DIR=$(xml_escape "$NODE_DIR")
77
- ESC_CDP=$(xml_escape "$CDP_SCRIPT")
78
- ESC_HOME=$(xml_escape "$HOME")
79
- ESC_CHROME=$(xml_escape "$CHROME_PATH")
80
- cat > ~/Library/LaunchAgents/io.github.lcjanke2020.cdp-mcp.plist <<PLIST
81
- <?xml version="1.0" encoding="UTF-8"?>
82
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
83
- "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
84
- <plist version="1.0">
85
- <dict>
86
- <key>Label</key>
87
- <string>io.github.lcjanke2020.cdp-mcp</string>
88
- <key>ProgramArguments</key>
89
- <array>
90
- <string>$ESC_NODE</string>
91
- <string>$ESC_CDP</string>
92
- <string>--port</string>
93
- <string>9719</string>
94
- </array>
95
- <key>RunAtLoad</key>
96
- <true/>
97
- <key>KeepAlive</key>
98
- <true/>
99
- <key>StandardOutPath</key>
100
- <string>$ESC_HOME/Library/Logs/cdp-mcp/server.stdout.log</string>
101
- <key>StandardErrorPath</key>
102
- <string>$ESC_HOME/Library/Logs/cdp-mcp/server.stderr.log</string>
103
- <key>EnvironmentVariables</key>
104
- <dict>
105
- <key>PATH</key>
106
- <string>$ESC_NODE_DIR:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
107
- $(if [ -n "$CHROME_PATH" ]; then printf ' <key>CHROME_PATH</key>\n <string>%s</string>\n' "$ESC_CHROME"; fi)
108
- </dict>
109
- </dict>
110
- </plist>
111
- PLIST
112
- ```
113
-
114
- The plist invokes `node` directly with the `cdp-mcp` script path. That makes the
115
- `NODE_BIN` override authoritative even when your shell uses a Node version
116
- manager.
117
-
118
- ## 3. Load and start
119
-
120
- On macOS 10.15+, use:
121
-
122
- ```bash
123
- launchctl bootstrap gui/$UID ~/Library/LaunchAgents/io.github.lcjanke2020.cdp-mcp.plist
124
- launchctl kickstart -k gui/$UID/io.github.lcjanke2020.cdp-mcp
125
- ```
126
-
127
- Older macOS releases also support:
128
-
129
- ```bash
130
- launchctl load ~/Library/LaunchAgents/io.github.lcjanke2020.cdp-mcp.plist
131
- ```
132
-
133
- ## 4. Verify
134
-
135
- ```bash
136
- launchctl print gui/$UID/io.github.lcjanke2020.cdp-mcp
137
- lsof -i :9719
138
- curl -v --max-time 2 http://127.0.0.1:9719/sse 2>&1 | head -20
139
- ```
140
-
141
- The `curl` command should show a `200 OK` response and SSE event output. A
142
- timeout after the first event is expected because `/sse` keeps the connection
143
- open. The server also sends periodic SSE keepalive comments by default; tune
144
- with `CDP_MCP_SSE_KEEPALIVE_MS` only if your MCP client needs a different idle
145
- interval.
146
-
147
- ## 5. Configure an MCP client
148
-
149
- Point an SSE-capable MCP client at:
150
-
151
- ```text
152
- http://127.0.0.1:9719/sse
153
- ```
154
-
155
- For example, clients that use JSON MCP server config commonly use:
156
-
157
- ```json
158
- {
159
- "mcpServers": {
160
- "cdp-mcp": {
161
- "type": "sse",
162
- "url": "http://127.0.0.1:9719/sse"
163
- }
164
- }
165
- }
166
- ```
167
-
168
- SSE mode is single-client today. Multiple MCP clients connected to the same
169
- service share one process-global browser/CDP session and can interfere with each
170
- other. Use one active debugging client per service, or run separate services on
171
- separate ports.
172
-
173
- A reconnecting client resumes the prior session. If you want a clean browser
174
- session after reconnecting, call `close_session` before launching or attaching
175
- again.
176
-
177
- ## 6. Logs
178
-
179
- ```bash
180
- tail -f ~/Library/Logs/cdp-mcp/server.stderr.log
181
- ```
182
-
183
- ## 7. Stop / uninstall
184
-
185
- ```bash
186
- launchctl bootout gui/$UID/io.github.lcjanke2020.cdp-mcp
187
- rm ~/Library/LaunchAgents/io.github.lcjanke2020.cdp-mcp.plist
188
- ```
189
-
190
- Older macOS releases also support:
191
-
192
- ```bash
193
- launchctl unload ~/Library/LaunchAgents/io.github.lcjanke2020.cdp-mcp.plist
194
- ```
195
-
196
- ## 8. Upgrade
197
-
198
- ```bash
199
- npm install -g cdp-mcp@latest
200
- launchctl kickstart -k gui/$UID/io.github.lcjanke2020.cdp-mcp
201
- ```
202
-
203
- Restart or reconnect your MCP client after a server upgrade so it reloads tool
204
- schemas.
205
-
206
- ## Troubleshooting
207
-
208
- | Symptom | Fix |
209
- |---|---|
210
- | `bootstrap` says service already loaded | Run `launchctl bootout gui/$UID/io.github.lcjanke2020.cdp-mcp`, then bootstrap again |
211
- | Service exits immediately | Check `~/Library/Logs/cdp-mcp/server.stderr.log`; usually `cdp-mcp` is not installed, Node is too old, or a version-manager path moved |
212
- | Port 9719 is already in use | Check `lsof -i :9719`, then stop the other process or change the port in the plist |
213
- | MCP client rejects the config | Confirm the client supports SSE MCP servers and include both `"type": "sse"` and the `/sse` URL if your client uses JSON config |
214
- | `launch_chrome` cannot find Chrome | Set `CHROME_PATH` before generating the plist, or edit the plist environment and reload the service |
215
- | Service not starting after reboot | Verify the plist is in `~/Library/LaunchAgents/`, not `LaunchDaemons` |
216
- | Service not starting after reboot with fnm/nvm | Version-manager shell paths can be ephemeral. Recreate the plist with stable `NODE_BIN` and `CDP_SCRIPT` paths, or install with a system Node |
217
- | `already_session` after reconnecting | The prior browser/CDP session is still alive. Resume it, or call `close_session` before starting fresh |
@@ -1,199 +0,0 @@
1
- # Local L3 e2e setup (Playwright Chromium + AppArmor)
2
-
3
- **Last updated: 2026-06-09**
4
-
5
- A step-by-step runbook for getting `npm run test:e2e` (the L3 real-browser
6
- suite) passing on a local Linux machine **with Chromium's sandbox on**. This is
7
- the practical companion to [`chromium-sandboxing.md`](./chromium-sandboxing.md);
8
- read that for the full `--no-sandbox` / `sandbox: true` threat model and the
9
- validated-hosts table.
10
-
11
- Assumes Ubuntu (23.10+/24.04). Other distributions may need no host-side work at
12
- all — see "Other distributions" below.
13
-
14
- ## Why this is needed on Ubuntu
15
-
16
- The L3 e2e harness launches Chromium with the sandbox **on** when running
17
- locally; it only adds `--no-sandbox` when the `CI` env var is set
18
- (`test/e2e/setup/global.ts`). That is deliberate — locally we want the sandbox
19
- when the host can provide it.
20
-
21
- But recent Ubuntu releases ship:
22
-
23
- ```sh
24
- kernel.apparmor_restrict_unprivileged_userns = 1
25
- ```
26
-
27
- which blocks the unprivileged **user namespace** that Chromium's sandbox needs.
28
- Playwright-bundled Chromium does not ship a SUID `chrome_sandbox` helper, so on a
29
- stock Ubuntu host a sandbox-on launch fails before the DevTools port opens:
30
-
31
- ```text
32
- zygote_host_impl_linux.cc: No usable sandbox!
33
- ```
34
-
35
- From `chrome-launcher` this usually surfaces as a startup port-poll timeout or
36
- `ECONNREFUSED`.
37
-
38
- The fix is an AppArmor profile that grants `userns,` to the Playwright Chromium
39
- binary, giving it a stable named label that is allowed to create the user
40
- namespace. The steps below install Chromium, confirm the resolver finds it,
41
- attach the profile, and run the suite.
42
-
43
- ## 1. Install Playwright Chromium
44
-
45
- From the repo:
46
-
47
- ```sh
48
- npx --yes playwright install chromium
49
- ```
50
-
51
- This drops a managed Chromium into the per-user cache:
52
-
53
- ```text
54
- ~/.cache/ms-playwright/chromium-<rev>/chrome-linux*/chrome
55
- ```
56
-
57
- The leaf directory varies by Playwright version and arch — `chrome-linux` on
58
- ARM64 and older builds, `chrome-linux64` on x86_64 with the newer
59
- Chrome-for-Testing layout (the resolver and the AppArmor glob below cover both).
60
- Install it for **each OS user** that will run the suite — the cache is
61
- per-`$HOME`.
62
-
63
- ## 2. Verify the resolver finds it
64
-
65
- The launcher resolver (`src/util/browser-resolve.ts`) finds Chromium in this
66
- order: an explicit `CDP_TEST_BROWSER_PATH`, then a system `chromium` on `PATH`,
67
- then the Playwright cache. After a build, confirm what it picks:
68
-
69
- ```sh
70
- npm run build
71
- node --input-type=module \
72
- -e "import('./dist/util/browser-resolve.js').then(m => console.log(JSON.stringify(m.resolveBrowser(), null, 2)))"
73
- ```
74
-
75
- This runbook targets the **Playwright-cache** binary, so expect
76
- `source: "playwright-cache"` and a `binaryPath` under `~/.cache/ms-playwright/`:
77
-
78
- ```json
79
- {
80
- "binaryPath": "/home/<user>/.cache/ms-playwright/chromium-<rev>/chrome-linux/chrome",
81
- "choice": "chromium",
82
- "snapConfined": false,
83
- "source": "playwright-cache"
84
- }
85
- ```
86
-
87
- If you have a system Chromium on `PATH` (apt `/usr/bin/chromium`, snap
88
- `/snap/bin/chromium`), the resolver returns that first with
89
- `source: "which-chromium"` — that binary is *not* covered by the AppArmor
90
- profile below (snap brings its own confinement; apt Chromium is a separate
91
- sandbox story). To exercise the Playwright binary under this profile, point the
92
- suite at it explicitly:
93
-
94
- ```sh
95
- export CDP_TEST_BROWSER_PATH="$(ls -d ~/.cache/ms-playwright/chromium-*/chrome-linux*/chrome | head -1)"
96
- ```
97
-
98
- ## 3. Attach the AppArmor profile
99
-
100
- First confirm the kernel knob is the restrictive default:
101
-
102
- ```sh
103
- sysctl kernel.apparmor_restrict_unprivileged_userns # = 1 on stock Ubuntu 24.04
104
- ```
105
-
106
- If it is `0` (some hosts turn it off system-wide), Chromium's sandbox already
107
- works and you can skip to step 5.
108
-
109
- Create a profile that grants `userns,` to the Playwright Chromium binary path.
110
- This mirrors the shape of Ubuntu's stock `chrome` / `msedge` / `brave` profiles
111
- (a named-unconfined profile that opts into user namespaces):
112
-
113
- ```apparmor
114
- # /etc/apparmor.d/cdp-mcp-chromium
115
- abi <abi/4.0>,
116
- include <tunables/global>
117
-
118
- profile cdp-mcp-chromium /home/*/.cache/ms-playwright/chromium-*/chrome-linux*/chrome flags=(unconfined) {
119
- userns,
120
-
121
- include if exists <local/cdp-mcp-chromium>
122
- }
123
- ```
124
-
125
- The `chrome-linux*` component matches both the `chrome-linux` (ARM64/older) and
126
- `chrome-linux64` (x86_64 Chrome-for-Testing) layouts — in AppArmor `*` matches
127
- within a single path segment, so a too-specific `chrome-linux` would silently
128
- fail to attach on x86_64. The `/home/*/` glob matches any user's Playwright
129
- cache; if you prefer to scope it to specific accounts, replace `*` with a brace
130
- list of usernames, e.g. `/home/{alice,bob}/.cache/...`.
131
-
132
- Load it (profiles in `/etc/apparmor.d/` also auto-load at boot):
133
-
134
- ```sh
135
- sudo apparmor_parser -r /etc/apparmor.d/cdp-mcp-chromium
136
- ```
137
-
138
- ## 4. Verify the label attaches
139
-
140
- Launch the bundled Chromium sandbox-on and read its AppArmor label — it must be
141
- the named profile, not bare `unconfined`:
142
-
143
- ```sh
144
- BIN=$(ls -d ~/.cache/ms-playwright/chromium-*/chrome-linux*/chrome | head -1)
145
- "$BIN" --headless=new --no-startup-window --remote-debugging-port=0 \
146
- --user-data-dir=$(mktemp -d) about:blank & pid=$!
147
- sleep 3; cat /proc/$pid/attr/current # -> cdp-mcp-chromium (unconfined)
148
- kill $pid
149
- ```
150
-
151
- If this prints `cdp-mcp-chromium (unconfined)`, the profile is attached. A bare
152
- `unconfined` means the binary path didn't match the profile's glob — re-check
153
- the cache path against the profile.
154
-
155
- ## 5. Run L3
156
-
157
- ```sh
158
- npm run test:e2e
159
- ```
160
-
161
- With the sandbox on and the profile attached, the suite should pass, e.g.:
162
-
163
- ```text
164
- Test Files 10 passed (10)
165
- Tests 29 passed (29)
166
- ```
167
-
168
- ## Fallback (before AppArmor is configured)
169
-
170
- The L3 harness adds `--no-sandbox` when `CI` is set, so you can run the suite
171
- without the profile as a lower-security stopgap:
172
-
173
- ```sh
174
- env CI=1 npm run test:e2e
175
- ```
176
-
177
- This keeps work moving, but the AppArmor profile is the desired long-term
178
- posture so that plain `npm run test:e2e` exercises sandbox-on Chromium. See
179
- [`chromium-sandboxing.md`](./chromium-sandboxing.md) for why `--no-sandbox`
180
- widens the blast radius of a compromised renderer.
181
-
182
- ## Other distributions
183
-
184
- This profile work is Ubuntu-specific. Distributions that don't restrict
185
- unprivileged user namespaces by default (or use SELinux instead of AppArmor,
186
- e.g. Fedora) generally run sandbox-on Chromium without a host-side profile.
187
- Validate the actual host before assuming the Ubuntu steps are required — check
188
- `sysctl kernel.apparmor_restrict_unprivileged_userns` (absent or `0` means no
189
- AppArmor userns restriction to work around).
190
-
191
- ## Related
192
-
193
- - [`docs/chromium-sandboxing.md`](./chromium-sandboxing.md) — the canonical
194
- `--no-sandbox` / `sandbox: true` threat model, the AppArmor / userns / snap /
195
- Bubblewrap mechanism map, and the validated-hosts table.
196
- - [`docs/known-chromium-gaps.md`](./known-chromium-gaps.md) — per-spec
197
- Chromium-vs-Chrome gaps and host-OS workarounds.
198
- - [README §L3](../README.md) — browser selection, `CDP_TEST_BROWSER_PATH`, and
199
- the per-platform support matrix.
@@ -1,233 +0,0 @@
1
- # Linux: Run as a Persistent Service (systemd)
2
-
3
- Register `cdp-mcp` as a systemd user service so it starts automatically on login
4
- and exposes the MCP SSE endpoint on `127.0.0.1:9719`. If you enable lingering,
5
- the service can also start at boot before an interactive login.
6
-
7
- Persistent service mode is useful for MCP clients that support SSE because the
8
- `cdp-mcp` process and its browser/CDP session can survive MCP client restarts or
9
- reconnects. It does **not** persist state across service-process restarts.
10
-
11
- > Security note: the local SSE endpoint has no authentication. MCP tools include
12
- > in-page JavaScript evaluation and filesystem writes via screenshot paths. Only
13
- > run a persistent service on trusted single-user machines. Be especially careful
14
- > with `loginctl enable-linger` on shared hosts because it widens the service's
15
- > exposure window beyond your interactive login session.
16
-
17
- ## Contents
18
-
19
- - [1. Install the server](#1-install-the-server)
20
- - [2. Optional: enable lingering](#2-optional-enable-lingering)
21
- - [3. Create the unit file](#3-create-the-unit-file)
22
- - [4. Enable and start the service](#4-enable-and-start-the-service)
23
- - [5. Verify](#5-verify)
24
- - [6. Configure an MCP client](#6-configure-an-mcp-client)
25
- - [7. Logs](#7-logs)
26
- - [8. Stop / uninstall](#8-stop--uninstall)
27
- - [9. Upgrade](#9-upgrade)
28
- - [Linux ARM64 / Chromium](#linux-arm64--chromium)
29
- - [Troubleshooting](#troubleshooting)
30
-
31
- ## 1. Install the server
32
-
33
- Requires Node.js 20+ and a local Chrome/Chromium browser.
34
-
35
- ```bash
36
- npm install -g cdp-mcp
37
- ```
38
-
39
- Verify with `cdp-mcp --help`. The package ships prebuilt `dist/`, so there is no
40
- build step and no repo checkout needed.
41
-
42
- If `launch_chrome` cannot find Chrome/Chromium automatically, set `CHROME_PATH`
43
- when generating the unit file below.
44
-
45
- ## 2. Optional: enable lingering
46
-
47
- Enable lingering only if you want the user service to start at boot even before
48
- you log in:
49
-
50
- ```bash
51
- sudo loginctl enable-linger "$USER"
52
- ```
53
-
54
- You only need to run this once per machine. Check with:
55
-
56
- ```bash
57
- loginctl show-user "$USER" --property=Linger
58
- ```
59
-
60
- Skip this step if starting the service during your login session is enough.
61
-
62
- ## 3. Create the unit file
63
-
64
- Run this from any directory:
65
-
66
- ```bash
67
- # If you use fnm, nvm, or another Node version manager, set these variables to
68
- # stable paths before running this snippet. Example:
69
- # NODE_BIN="$HOME/.local/share/fnm/aliases/default/bin/node"
70
- # CDP_SCRIPT="$HOME/.local/share/fnm/aliases/default/bin/cdp-mcp"
71
- NODE_BIN="${NODE_BIN:-$(command -v node)}"
72
- CDP_SCRIPT="${CDP_SCRIPT:-$(command -v cdp-mcp)}"
73
- CHROME_PATH="${CHROME_PATH:-}"
74
-
75
- if [ -z "$NODE_BIN" ]; then
76
- echo "Error: node not found in PATH. Install Node 20+ first." >&2
77
- exit 1
78
- fi
79
- if [ -z "$CDP_SCRIPT" ]; then
80
- echo "Error: cdp-mcp not found. Run 'npm install -g cdp-mcp' first." >&2
81
- exit 1
82
- fi
83
-
84
- NODE_DIR="$(dirname "$NODE_BIN")"
85
- mkdir -p ~/.config/systemd/user
86
- cat > ~/.config/systemd/user/cdp-mcp.service << EOF
87
- [Unit]
88
- Description=cdp-mcp browser MCP server (SSE on port 9719)
89
- After=network.target
90
-
91
- [Service]
92
- Type=simple
93
- ExecStart="${NODE_BIN}" "${CDP_SCRIPT}" --port 9719
94
- Restart=on-failure
95
- RestartSec=5
96
- Environment="PATH=${NODE_DIR}:/usr/local/bin:/usr/bin:/bin"
97
- $(if [ -n "$CHROME_PATH" ]; then printf 'Environment="CHROME_PATH=%s"\n' "$CHROME_PATH"; else printf '# Optional: set CHROME_PATH if launch_chrome cannot find Chrome/Chromium.\n# Environment="CHROME_PATH=/path/to/chrome"\n'; fi)
98
-
99
- [Install]
100
- WantedBy=default.target
101
- EOF
102
- ```
103
-
104
- The unit invokes `node` directly with the `cdp-mcp` script path. That makes the
105
- `NODE_BIN` override authoritative even when your shell uses a Node version
106
- manager. The `ExecStart` and `Environment` values are double-quoted so systemd
107
- treats a path containing spaces as a single token rather than splitting it.
108
-
109
- ## 4. Enable and start the service
110
-
111
- ```bash
112
- systemctl --user daemon-reload
113
- systemctl --user enable --now cdp-mcp.service
114
- ```
115
-
116
- ## 5. Verify
117
-
118
- ```bash
119
- systemctl --user status cdp-mcp.service
120
- ss -tlnp | grep 9719
121
- curl -s --max-time 2 http://127.0.0.1:9719/sse | head -1
122
- ```
123
-
124
- The `curl` command should print an SSE `event:` line. The stream stays open by
125
- design. The server also sends periodic SSE keepalive comments by default; tune
126
- with `CDP_MCP_SSE_KEEPALIVE_MS` only if your MCP client needs a different idle
127
- interval.
128
-
129
- ## 6. Configure an MCP client
130
-
131
- Point an SSE-capable MCP client at:
132
-
133
- ```text
134
- http://127.0.0.1:9719/sse
135
- ```
136
-
137
- For example, clients that use JSON MCP server config commonly use:
138
-
139
- ```json
140
- {
141
- "mcpServers": {
142
- "cdp-mcp": {
143
- "type": "sse",
144
- "url": "http://127.0.0.1:9719/sse"
145
- }
146
- }
147
- }
148
- ```
149
-
150
- SSE mode is single-client today. Multiple MCP clients connected to the same
151
- service share one process-global browser/CDP session and can interfere with each
152
- other. Use one active debugging client per service, or run separate services on
153
- separate ports.
154
-
155
- A reconnecting client resumes the prior session. If you want a clean browser
156
- session after reconnecting, call `close_session` before launching or attaching
157
- again.
158
-
159
- ## 7. Logs
160
-
161
- ```bash
162
- journalctl --user -u cdp-mcp.service -f
163
- journalctl --user -u cdp-mcp.service -n 100
164
- ```
165
-
166
- ## 8. Stop / uninstall
167
-
168
- ```bash
169
- systemctl --user stop cdp-mcp.service
170
- systemctl --user disable cdp-mcp.service
171
- rm ~/.config/systemd/user/cdp-mcp.service
172
- systemctl --user daemon-reload
173
- ```
174
-
175
- ## 9. Upgrade
176
-
177
- ```bash
178
- npm install -g cdp-mcp@latest
179
- systemctl --user restart cdp-mcp.service
180
- ```
181
-
182
- Restart or reconnect your MCP client after a server upgrade so it reloads tool
183
- schemas.
184
-
185
- ## Linux ARM64 / Chromium
186
-
187
- Google does not publish official Chrome builds for Linux ARM64. If your distro's
188
- Chromium package is unreliable for DevTools Protocol launches, use a
189
- Playwright-cached Chromium binary and set `CHROME_PATH` when generating the
190
- unit:
191
-
192
- ```bash
193
- # Install Playwright's Chromium (one-time):
194
- npx playwright install chromium
195
-
196
- # Set CHROME_PATH to the latest revision before running the unit-file script:
197
- export CHROME_PATH="$HOME/.cache/ms-playwright/chromium-1223/chrome-linux/chrome"
198
- ```
199
-
200
- Snap Chromium (`/snap/bin/chromium`) can be unreliable for persistent services
201
- because snap confinement may interfere with `--remote-debugging-port`, headless
202
- flags, and process lifecycle management. A Playwright-cached Chromium is often
203
- more predictable for CDP-based debugging sessions. The generated unit's `PATH`
204
- does not include `/snap/bin`, so if you do use snap Chromium you must set
205
- `CHROME_PATH=/snap/bin/chromium` explicitly — `launch_chrome` will not
206
- auto-detect it under the service environment.
207
-
208
- Playwright upgrades may relocate the binary. After running
209
- `npx playwright install chromium`, check the new revision directory name (for
210
- example, `chromium-1223` to `chromium-1250`), update `CHROME_PATH` in the unit
211
- file, and run:
212
-
213
- ```bash
214
- systemctl --user daemon-reload
215
- systemctl --user restart cdp-mcp.service
216
- ```
217
-
218
- For Chromium sandbox flags (`--no-sandbox`, AppArmor, snap confinement) and known
219
- host-OS launch gaps, see [chromium-sandboxing.md](./chromium-sandboxing.md) and
220
- [known-chromium-gaps.md](./known-chromium-gaps.md).
221
-
222
- ## Troubleshooting
223
-
224
- | Symptom | Fix |
225
- |---|---|
226
- | Service exits immediately | Check `journalctl --user -u cdp-mcp.service -n 100`; usually `cdp-mcp` is not installed, Node is too old, or a version-manager path moved |
227
- | Port 9719 is already in use | Compare `systemctl --user show -p MainPID --value cdp-mcp.service` with `ss -tlnp \| grep 9719`, then stop the other process or change the port |
228
- | MCP client rejects the config | Confirm the client supports SSE MCP servers and include both `"type": "sse"` and the `/sse` URL if your client uses JSON config |
229
- | `launch_chrome` cannot find Chrome | Set `CHROME_PATH` in the unit file and restart the service; on Linux ARM64, try Playwright-cached Chromium (`~/.cache/ms-playwright/chromium-*/chrome-linux/chrome`) |
230
- | Service not starting after reboot | Enable lingering with `sudo loginctl enable-linger "$USER"` |
231
- | Node not found after reboot with fnm/nvm | Version-manager shell paths can be ephemeral. Recreate the unit with stable `NODE_BIN` and `CDP_SCRIPT` paths, or install with a system Node |
232
- | `Failed to connect to bus` over SSH | Run `export XDG_RUNTIME_DIR=/run/user/$(id -u)` before using `systemctl --user` |
233
- | `already_session` after reconnecting | The prior browser/CDP session is still alive. Resume it, or call `close_session` before starting fresh |