cdp-mcp 0.2.2 → 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.
- package/README.md +28 -303
- package/bin.js +23 -0
- package/contract.d.ts +1 -0
- package/contract.js +2 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +31 -73
- package/dist/contract.d.ts +0 -11
- package/dist/contract.js +0 -11
- package/dist/contract.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -402
- package/dist/index.js.map +0 -1
- package/dist/locator.d.ts +0 -108
- package/dist/locator.js +0 -176
- package/dist/locator.js.map +0 -1
- package/dist/server.d.ts +0 -2
- package/dist/server.js +0 -41
- package/dist/server.js.map +0 -1
- package/dist/session/browser.d.ts +0 -29
- package/dist/session/browser.js +0 -409
- package/dist/session/browser.js.map +0 -1
- package/dist/session/buffers.d.ts +0 -48
- package/dist/session/buffers.js +0 -43
- package/dist/session/buffers.js.map +0 -1
- package/dist/session/pause.d.ts +0 -21
- package/dist/session/pause.js +0 -99
- package/dist/session/pause.js.map +0 -1
- package/dist/session/state.d.ts +0 -54
- package/dist/session/state.js +0 -93
- package/dist/session/state.js.map +0 -1
- package/dist/sourcemap/loader.d.ts +0 -4
- package/dist/sourcemap/loader.js +0 -138
- package/dist/sourcemap/loader.js.map +0 -1
- package/dist/sourcemap/normalize.d.ts +0 -2
- package/dist/sourcemap/normalize.js +0 -59
- package/dist/sourcemap/normalize.js.map +0 -1
- package/dist/sourcemap/store.d.ts +0 -57
- package/dist/sourcemap/store.js +0 -196
- package/dist/sourcemap/store.js.map +0 -1
- package/dist/tools/_locator_runtime.d.ts +0 -31
- package/dist/tools/_locator_runtime.js +0 -243
- package/dist/tools/_locator_runtime.js.map +0 -1
- package/dist/tools/_register.d.ts +0 -2
- package/dist/tools/_register.js +0 -30
- package/dist/tools/_register.js.map +0 -1
- package/dist/tools/breakpoints.d.ts +0 -4
- package/dist/tools/breakpoints.js +0 -200
- package/dist/tools/breakpoints.js.map +0 -1
- package/dist/tools/console.d.ts +0 -2
- package/dist/tools/console.js +0 -48
- package/dist/tools/console.js.map +0 -1
- package/dist/tools/dom.d.ts +0 -2
- package/dist/tools/dom.js +0 -309
- package/dist/tools/dom.js.map +0 -1
- package/dist/tools/execution.d.ts +0 -29
- package/dist/tools/execution.js +0 -89
- package/dist/tools/execution.js.map +0 -1
- package/dist/tools/forms.d.ts +0 -8
- package/dist/tools/forms.js +0 -256
- package/dist/tools/forms.js.map +0 -1
- package/dist/tools/inspect.d.ts +0 -2
- package/dist/tools/inspect.js +0 -178
- package/dist/tools/inspect.js.map +0 -1
- package/dist/tools/nav.d.ts +0 -2
- package/dist/tools/nav.js +0 -136
- package/dist/tools/nav.js.map +0 -1
- package/dist/tools/network.d.ts +0 -2
- package/dist/tools/network.js +0 -137
- package/dist/tools/network.js.map +0 -1
- package/dist/tools/session.d.ts +0 -2
- package/dist/tools/session.js +0 -76
- package/dist/tools/session.js.map +0 -1
- package/dist/tools/source.d.ts +0 -2
- package/dist/tools/source.js +0 -63
- package/dist/tools/source.js.map +0 -1
- package/dist/tools/storage.d.ts +0 -2
- package/dist/tools/storage.js +0 -296
- package/dist/tools/storage.js.map +0 -1
- package/dist/util/browser-resolve.d.ts +0 -19
- package/dist/util/browser-resolve.js +0 -263
- package/dist/util/browser-resolve.js.map +0 -1
- package/dist/util/errors.d.ts +0 -7
- package/dist/util/errors.js +0 -12
- package/dist/util/errors.js.map +0 -1
- package/dist/util/format.d.ts +0 -20
- package/dist/util/format.js +0 -65
- package/dist/util/format.js.map +0 -1
- package/dist/util/log.d.ts +0 -6
- package/dist/util/log.js +0 -34
- package/dist/util/log.js.map +0 -1
- package/docs/chromium-sandboxing.md +0 -197
- package/docs/known-chromium-gaps.md +0 -138
- package/docs/launchd-service.md +0 -217
- package/docs/local-l3-e2e-setup.md +0 -199
- package/docs/systemd-service.md +0 -233
package/docs/launchd-service.md
DELETED
|
@@ -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/</\</g' \
|
|
68
|
-
-e 's/>/\>/g' \
|
|
69
|
-
-e 's/"/\"/g' \
|
|
70
|
-
-e "s/'/\'/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.
|
package/docs/systemd-service.md
DELETED
|
@@ -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 |
|