smooth-operator-mcp 3.2.0 → 3.3.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 +80 -82
- package/dist/smooth-operator.mjs +4548 -4635
- package/dist/smooth-operator.mjs.map +3 -3
- package/docs/STEALTH-GUIDE.md +29 -21
- package/docs/architecture.md +64 -0
- package/docs/config.md +54 -0
- package/docs/harnesses.md +1 -1
- package/docs/mcp-server.md +54 -36
- package/docs/security.md +58 -0
- package/docs/tools.md +90 -0
- package/package.json +10 -6
package/docs/STEALTH-GUIDE.md
CHANGED
|
@@ -10,28 +10,33 @@ deterministic input.
|
|
|
10
10
|
|
|
11
11
|
The installer asks exactly three questions: browser profile ownership, browser
|
|
12
12
|
display, and the Chromium executable. Managed mode owns one private persistent
|
|
13
|
-
profile below `SMOOTH_OPERATOR_DATA_DIR
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
profile below `SMOOTH_OPERATOR_DATA_DIR`. Wizard Personal Chrome launches a
|
|
14
|
+
dedicated debugging profile and does not take over daily Chrome. A profile is
|
|
15
|
+
not a privacy boundary from the websites it visits, so use a dedicated profile
|
|
16
|
+
and the narrowest policy that fits the task.
|
|
17
17
|
|
|
18
18
|
## Optional controls
|
|
19
19
|
|
|
20
|
-
`SMOOTH_OPERATOR_STEALTH_ENABLED=true` is retained as a compatibility
|
|
21
|
-
It
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
`SMOOTH_OPERATOR_STEALTH_ENABLED=true` is retained as a compatibility label.
|
|
21
|
+
It does not hide automation signals, inject a page script, or fabricate a user
|
|
22
|
+
agent, platform, browser version, language, client hints, WebGL, canvas, TLS,
|
|
23
|
+
or operating-system identity. An explicit viewport is applied through launch
|
|
24
|
+
`--window-size` and `page.setViewport`, independent of this flag.
|
|
25
25
|
|
|
26
26
|
`SMOOTH_OPERATOR_STEALTH_PROFILE=balanced` or `max` are accepted compatibility
|
|
27
|
-
labels
|
|
27
|
+
labels with no patch-set difference. `SMOOTH_OPERATOR_STEALTH_GPU=true`
|
|
28
28
|
adds GPU launch flags but is not an identity or coherence guarantee.
|
|
29
29
|
|
|
30
|
-
`SMOOTH_OPERATOR_BEHAVIOR_ENABLED` controls optional pointer
|
|
31
|
-
|
|
30
|
+
`SMOOTH_OPERATOR_BEHAVIOR_ENABLED` controls optional pointer and typing
|
|
31
|
+
timing wrappers. It defaults off for the fastest raw interaction
|
|
32
32
|
path. If enabled, timings are short, bounded, and cancellable; this is a
|
|
33
33
|
workflow choice, not a guarantee of human identity or site access.
|
|
34
34
|
|
|
35
|
+
Connect mode attaches to the configured `browserURL` / `wsEndpoint`. The
|
|
36
|
+
installer Personal Chrome helper uses a dedicated debugging profile
|
|
37
|
+
(`~/.smooth-operator/personal-chrome`) and does not take over daily Chrome.
|
|
38
|
+
Advanced `chrome://inspect` attach is documented in `mcp-server.md`.
|
|
39
|
+
|
|
35
40
|
## Bounded browser controls
|
|
36
41
|
|
|
37
42
|
`SMOOTH_OPERATOR_BROWSER_IDLE_TIMEOUT_MS` is disabled by default (`0`). When
|
|
@@ -51,24 +56,27 @@ are never captured by that journal.
|
|
|
51
56
|
animations, safe attributes, and shallow structure. It omits scripts, event
|
|
52
57
|
handler source, form values, password content, and arbitrary data attributes.
|
|
53
58
|
|
|
54
|
-
## Connected-
|
|
59
|
+
## Connected-harness challenge loop
|
|
55
60
|
|
|
56
61
|
`browser_challenge` is an evidence-only detector and is available by default.
|
|
57
62
|
Title, text, HTML, frame-source, and visible-marker inputs have independent
|
|
58
63
|
limits so one oversized evidence category cannot hide later challenge markers.
|
|
59
|
-
`browser_solve_challenge` is
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
`browser_solve_challenge` is one evidence cycle for the connected harness.
|
|
65
|
+
Each call collects a fresh challenge classification and bounded visual/state
|
|
66
|
+
evidence, including `attemptsRemaining`. The connected harness uses normal
|
|
67
|
+
browser actions and calls it again until the final classification explicitly
|
|
68
|
+
reports the challenge absent or `automation_exhausted`. The tool returns
|
|
69
|
+
screenshot data as MCP image content when requested. `present`, `unknown`,
|
|
70
|
+
or a failed probe is never success, and `browser_wait_for_human` is only an
|
|
66
71
|
explicit option after exhaustion.
|
|
67
72
|
|
|
68
73
|
`browser_wait_for_human` is an optional handoff for a person to complete a
|
|
69
74
|
visible challenge or sign-in step. It does not claim success without a fresh
|
|
70
75
|
final classification. The server does not rotate identities or open network,
|
|
71
|
-
file, or authentication permissions for challenge handling.
|
|
76
|
+
file, or authentication permissions for challenge handling. Launch args stay
|
|
77
|
+
native-identity: they do not set `navigator.webdriver` to false, invent
|
|
78
|
+
UA/platform/client-hint/WebGL/canvas strings, or advertise `HeadlessChrome` on
|
|
79
|
+
the headed path.
|
|
72
80
|
|
|
73
81
|
## Boundaries and responsible use
|
|
74
82
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
SmoothOperator is a standalone Node.js MCP browser server. The harness reasons.
|
|
4
|
+
The server validates, enforces policy twice, drives one Chromium profile, and
|
|
5
|
+
returns a bounded result envelope. There is no model SDK, planner loop,
|
|
6
|
+
packaged browser add-on, or arbitrary CDP/host-code tool.
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
MCP client
|
|
10
|
+
├─ stdio, or Streamable HTTP (`/mcp`) + sibling `/healthz`
|
|
11
|
+
└─ MCP registry (one catalog → tools, resources, prompts)
|
|
12
|
+
└─ ServerRuntime
|
|
13
|
+
├─ SecurityPolicy (edge + service, same implementation)
|
|
14
|
+
├─ BrowserService (exclusive queue + Puppeteer/CDP)
|
|
15
|
+
├─ ResearchService (bounded DuckDuckGo HTML)
|
|
16
|
+
└─ Logger (JSON stderr, redacted)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Module ownership
|
|
20
|
+
|
|
21
|
+
| Module | Owns |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `src/server/main.ts` | CLI dispatch: `server`, `install`, `doctor` |
|
|
24
|
+
| `src/server/cli.ts` | Help, version, doctor diagnostics |
|
|
25
|
+
| `src/server/http.ts` | Streamable HTTP, Host/Origin/bearer, `healthz`, shutdown bounds |
|
|
26
|
+
| `src/server/catalog.ts` | Locked 57-tool names, retired-name map, resources, prompts, instructions |
|
|
27
|
+
| `src/server/mcp.ts` | Registration and dispatch from the catalog |
|
|
28
|
+
| `src/server/envelope.ts` | One result envelope: bound, redact, explicit truncation |
|
|
29
|
+
| `src/server/contracts.ts` | Zod schemas shared by tools and `browser_batch` |
|
|
30
|
+
| `src/server/runtime.ts` | Lifecycle, exclusive profile lease, health |
|
|
31
|
+
| `src/server/policy.ts` | URL/DNS/file/eval gates used at the MCP edge and again at the service |
|
|
32
|
+
| `src/server/config.ts` | Env + JSON config, chmod-600 reads |
|
|
33
|
+
| `src/server/browser/service.ts` | Browser facade: execute, snapshot, tabs, doctor, four modes |
|
|
34
|
+
| `src/server/browser/queue.ts` | Exclusive/read lane, absolute queue deadline, abort recovery |
|
|
35
|
+
| `src/server/browser/challenges.ts` | Pure evidence → classification |
|
|
36
|
+
| `src/server/research.ts` | Bounded search, `redirect: "error"`, anti-bot reported not bypassed |
|
|
37
|
+
| `src/server/installer.ts` / `installer-wizard.ts` | Harness install, three-question wizard, atomic config IO |
|
|
38
|
+
| `src/server/errors.ts` / `logger.ts` / `security.ts` | AppError + recovery, JSON logs, untrusted wrappers |
|
|
39
|
+
|
|
40
|
+
## One of each
|
|
41
|
+
|
|
42
|
+
- **One catalog** feeds `tools/list` and handlers (`catalog.ts`).
|
|
43
|
+
- **One envelope** serializes success and error (`envelope.ts`). Text fallback equals structured content.
|
|
44
|
+
- **One policy implementation** (`SecurityPolicy`) is applied at the MCP edge (navigate/eval) and again inside `BrowserService` / research.
|
|
45
|
+
- **One exclusive profile lease** (`runtime.ts` lock file) plus one in-process operation queue (`browser/queue.ts`). A timed-out operation recovers the old lifecycle before the next waiter is released.
|
|
46
|
+
|
|
47
|
+
## Browser modes
|
|
48
|
+
|
|
49
|
+
| Mode | Process | Profile |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `managed` | Launch or reattach via `DevToolsActivePort` | `${DATA_DIR}/browser` |
|
|
52
|
+
| `connect` | Attach to configured `browserURL` / `wsEndpoint` | Wizard Personal Chrome uses `personal-chrome`; `chrome://inspect` daily-profile attach is advanced opt-in (`mcp-server.md`) |
|
|
53
|
+
| `launch` | Launch configured executable | Configured `userDataDir` |
|
|
54
|
+
| `disabled` | None | Browser tools fail `BROWSER_DISABLED`; policy still runs |
|
|
55
|
+
|
|
56
|
+
## Transports
|
|
57
|
+
|
|
58
|
+
Stdio is the default. HTTP binds loopback unless `SMOOTH_OPERATOR_ALLOW_REMOTE_HTTP=true` with a 32-character token. `GET/HEAD <path>/healthz` uses the same Host, Origin, and bearer checks as `/mcp` and returns `{ status, ready, server, transport, checks }` without page data.
|
|
59
|
+
|
|
60
|
+
## Verify
|
|
61
|
+
|
|
62
|
+
`npm run verify` is lint, typecheck, coverage tests, knip, and package smoke
|
|
63
|
+
(which builds). Coverage and `npm test` pass `--exclude tests/browser-live.test.ts`.
|
|
64
|
+
Live Chrome is only `npm run test:browser:live` (explicit `vitest run tests/browser-live.test.ts`) and is not inside verify.
|
package/docs/config.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Environment variables or `--config` JSON (`chmod 600`, no symlinks).
|
|
4
|
+
Comma-separated lists reject more than 128 entries before normalization.
|
|
5
|
+
|
|
6
|
+
| Variable | Default | Purpose |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| `SMOOTH_OPERATOR_TRANSPORT` | `stdio` | `stdio` or `http` |
|
|
9
|
+
| `SMOOTH_OPERATOR_CONFIG` | auto `~/.smooth-operator/config.json` | Explicit JSON path |
|
|
10
|
+
| `SMOOTH_OPERATOR_DATA_DIR` | `~/.smooth-operator` | Data root |
|
|
11
|
+
| `SMOOTH_OPERATOR_BROWSER_MODE` | `managed` | `managed` / `connect` / `launch` / `disabled` |
|
|
12
|
+
| `SMOOTH_OPERATOR_BROWSER_URL` | `http://127.0.0.1:9222` | DevTools HTTP endpoint |
|
|
13
|
+
| `SMOOTH_OPERATOR_BROWSER_WS_ENDPOINT` | unset | DevTools WebSocket endpoint |
|
|
14
|
+
| `SMOOTH_OPERATOR_BROWSER_EXECUTABLE` | unset | Chromium binary (required for `launch`) |
|
|
15
|
+
| `SMOOTH_OPERATOR_BROWSER_USER_DATA_DIR` | `${DATA_DIR}/browser` | Persistent profile |
|
|
16
|
+
| `SMOOTH_OPERATOR_BROWSER_HEADLESS` | `false` | Headless Chrome |
|
|
17
|
+
| `SMOOTH_OPERATOR_BROWSER_VIEWPORT_WIDTH` / `_HEIGHT` | unset | Set both for an explicit viewport |
|
|
18
|
+
| `SMOOTH_OPERATOR_BROWSER_AUTO_LAUNCH` | `false` | Legacy connect-mode recovery launch |
|
|
19
|
+
| `SMOOTH_OPERATOR_BROWSER_TIMEOUT_MS` | `15000` | Action timeout |
|
|
20
|
+
| `SMOOTH_OPERATOR_BROWSER_CONNECT_TIMEOUT_MS` | `30000` | Connect timeout |
|
|
21
|
+
| `SMOOTH_OPERATOR_BROWSER_CDP_TIMEOUT_MS` | `30000` | CDP timeout |
|
|
22
|
+
| `SMOOTH_OPERATOR_BROWSER_IDLE_TIMEOUT_MS` | `0` | Idle cleanup; `0` disabled, max 24 hours |
|
|
23
|
+
| `SMOOTH_OPERATOR_MAX_SCREENSHOT_BYTES` | `8000000` | Screenshot cap |
|
|
24
|
+
| `SMOOTH_OPERATOR_MAX_HTML_CHARS` | `200000` | HTML cap |
|
|
25
|
+
| `SMOOTH_OPERATOR_ALLOWED_DOMAINS` | unset | Hostname allowlist (`*.` suffix ok) |
|
|
26
|
+
| `SMOOTH_OPERATOR_BLOCKED_DOMAINS` | unset | Hostname denylist |
|
|
27
|
+
| `SMOOTH_OPERATOR_ALLOWED_FILE_ROOTS` | `files`,`downloads` | Upload/PDF roots |
|
|
28
|
+
| `SMOOTH_OPERATOR_ALLOW_PRIVATE_NETWORK` | `false` | Non-loopback private targets |
|
|
29
|
+
| `SMOOTH_OPERATOR_ALLOW_EVAL` | `true` | Page JavaScript |
|
|
30
|
+
| `SMOOTH_OPERATOR_STEALTH_ENABLED` | `true` | Compatibility label; identity stays native |
|
|
31
|
+
| `SMOOTH_OPERATOR_STEALTH_PROFILE` | `balanced` | `balanced` or `max`; no patch-set difference |
|
|
32
|
+
| `SMOOTH_OPERATOR_STEALTH_GPU` | `false` | Opt-in GPU flags |
|
|
33
|
+
| `SMOOTH_OPERATOR_BEHAVIOR_ENABLED` | `false` | Opt-in timing wrappers |
|
|
34
|
+
| `SMOOTH_OPERATOR_HTTP_HOST` | `127.0.0.1` | HTTP bind host |
|
|
35
|
+
| `SMOOTH_OPERATOR_HTTP_PORT` | `3344` | HTTP bind port |
|
|
36
|
+
| `SMOOTH_OPERATOR_HTTP_PATH` | `/mcp` | MCP path; health is `<path>/healthz` |
|
|
37
|
+
| `SMOOTH_OPERATOR_HTTP_TOKEN` | unset | Bearer token |
|
|
38
|
+
| `SMOOTH_OPERATOR_ALLOW_REMOTE_HTTP` | `false` | Non-loopback HTTP |
|
|
39
|
+
| `SMOOTH_OPERATOR_HTTP_MAX_BODY_BYTES` | `2000000` | Body cap |
|
|
40
|
+
| `SMOOTH_OPERATOR_ALLOWED_HOSTS` / `_ORIGINS` | localhost, loopback | HTTP allowlists |
|
|
41
|
+
| `SMOOTH_OPERATOR_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
|
|
42
|
+
|
|
43
|
+
Removed (fail closed if set): `SMOOTH_OPERATOR_BROWSER_PROFILE`,
|
|
44
|
+
`SMOOTH_OPERATOR_BROWSER_STEALTH`, `SMOOTH_OPERATOR_DEFAULT_MODE`,
|
|
45
|
+
`SMOOTH_OPERATOR_BROWSER_USER_AGENT`.
|
|
46
|
+
|
|
47
|
+
CLI: `smooth-operator [server] [--transport stdio|http] [--config path] [--host host] [--port port]`,
|
|
48
|
+
`smooth-operator install [harness] --yes`, `smooth-operator doctor`.
|
|
49
|
+
|
|
50
|
+
The wizard asks exactly three questions: browser profile ownership, browser
|
|
51
|
+
display, and the Chromium executable. `--yes` uses managed, headed, eval on,
|
|
52
|
+
native-identity compatibility, and deterministic input.
|
|
53
|
+
|
|
54
|
+
See `.env.example` for a copy-paste template.
|
package/docs/harnesses.md
CHANGED
|
@@ -58,7 +58,7 @@ npm install -g .
|
|
|
58
58
|
smooth-operator install --help
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
After installation, restart the harness and confirm the
|
|
61
|
+
After installation, restart the harness and confirm the 57-tool registry is
|
|
62
62
|
visible. `server_health` reports the active capabilities; `browser_doctor`
|
|
63
63
|
reports whether the configured or discovered executable is launch-ready and
|
|
64
64
|
whether a managed DevTools endpoint is live. Optional idle cleanup is disabled
|
package/docs/mcp-server.md
CHANGED
|
@@ -25,11 +25,15 @@ MCP client
|
|
|
25
25
|
└─ Logger and safe error boundary
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
`src/server/main.ts` owns
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`src/server/
|
|
28
|
+
`src/server/main.ts` owns CLI dispatch (`server`, `install`, `doctor`).
|
|
29
|
+
`src/server/http.ts` owns Streamable HTTP, `healthz`, and shutdown bounds.
|
|
30
|
+
`src/server/catalog.ts` is the locked public surface; `src/server/mcp.ts`
|
|
31
|
+
registers it. `src/server/envelope.ts` serializes one result envelope.
|
|
32
|
+
`src/server/runtime.ts` owns dependency lifecycle and the profile lease.
|
|
33
|
+
Browser operations are in `src/server/browser/service.ts` with the exclusive
|
|
34
|
+
queue in `src/server/browser/queue.ts`. Policy and configuration are in
|
|
35
|
+
`src/server/policy.ts` and `src/server/config.ts`. See
|
|
36
|
+
[architecture.md](architecture.md) for the module table.
|
|
33
37
|
|
|
34
38
|
## Install and start
|
|
35
39
|
|
|
@@ -326,8 +330,8 @@ variables include:
|
|
|
326
330
|
| `SMOOTH_OPERATOR_ALLOWED_FILE_ROOTS` | data `files`, `downloads` | Explicit roots replace defaults |
|
|
327
331
|
| `SMOOTH_OPERATOR_ALLOW_PRIVATE_NETWORK` | `false` | Allows non-loopback private targets when true |
|
|
328
332
|
| `SMOOTH_OPERATOR_ALLOW_EVAL` | `true` | Set `false` to disable page JavaScript |
|
|
329
|
-
| `SMOOTH_OPERATOR_STEALTH_ENABLED` | `true` |
|
|
330
|
-
| `SMOOTH_OPERATOR_STEALTH_PROFILE` | `balanced` | `balanced` or `max
|
|
333
|
+
| `SMOOTH_OPERATOR_STEALTH_ENABLED` | `true` | Compatibility label; identity stays native |
|
|
334
|
+
| `SMOOTH_OPERATOR_STEALTH_PROFILE` | `balanced` | `balanced` or `max`; no patch-set difference |
|
|
331
335
|
| `SMOOTH_OPERATOR_STEALTH_GPU` | `false` | Adds opt-in GPU launch flags |
|
|
332
336
|
| `SMOOTH_OPERATOR_BEHAVIOR_ENABLED` | `false` | Opt-in timing wrappers |
|
|
333
337
|
| `SMOOTH_OPERATOR_HTTP_HOST` | `127.0.0.1` | HTTP bind host |
|
|
@@ -359,8 +363,9 @@ loop is introduced.
|
|
|
359
363
|
Raw MCP/tool-call speed is not the main bot-detection vector. Sites can score
|
|
360
364
|
network and browser identity, IP reputation, session history, and interaction
|
|
361
365
|
timing independently. A faster call does not bypass a challenge or make an
|
|
362
|
-
automated session legitimate
|
|
363
|
-
only where the target
|
|
366
|
+
automated session legitimate. The connected harness loops
|
|
367
|
+
`browser_solve_challenge` / `browser_wait_for_human` only where the target
|
|
368
|
+
permits automation.
|
|
364
369
|
|
|
365
370
|
## Security enforcement layers
|
|
366
371
|
|
|
@@ -395,9 +400,10 @@ are always applied, with explicit opt-ins where documented:
|
|
|
395
400
|
- Page text, HTML, titles, attributes, search snippets, cookies, and logs are
|
|
396
401
|
treated as untrusted data, normalized, bounded, and redacted before output.
|
|
397
402
|
- Challenge and anti-bot markers are reported from bounded evidence. The server
|
|
398
|
-
does not rotate identities or silently bypass challenges. The connected
|
|
399
|
-
|
|
400
|
-
|
|
403
|
+
does not rotate identities or silently bypass challenges. The connected harness
|
|
404
|
+
loops `browser_solve_challenge` / `browser_wait_for_human`: each solve call
|
|
405
|
+
collects fresh classification and visual/state evidence, allows ordinary
|
|
406
|
+
browser actions, and verifies with a subsequent call.
|
|
401
407
|
|
|
402
408
|
Run the server with a dedicated browser profile and the smallest domain and
|
|
403
409
|
file-root allowlists that fit the task. Browser automation can still perform
|
|
@@ -408,32 +414,37 @@ for confirming destructive calls.
|
|
|
408
414
|
|
|
409
415
|
### Tools
|
|
410
416
|
|
|
411
|
-
The registry exposes
|
|
412
|
-
individual descriptions and limits are returned by `tools/list`.
|
|
417
|
+
The registry exposes 57 public tools in these groups. Every input is schema-validated;
|
|
418
|
+
individual descriptions and limits are returned by `tools/list`. Each listed
|
|
419
|
+
name does one job. Cookies: `get`/`set`/`delete`. Storage and resource-blocking:
|
|
420
|
+
`get`/`set`/`clear`. Network and console logs:
|
|
421
|
+
`enable`/`disable`/`read`/`clear`/`read_and_clear`. Dialogs:
|
|
422
|
+
`get_text`/`accept`/`dismiss`/`send_keys`.
|
|
413
423
|
|
|
414
424
|
**Observation and extraction:** `browser_snapshot`, `browser_tabs`,
|
|
415
|
-
`
|
|
425
|
+
`browser_list_sessions`,
|
|
416
426
|
`browser_page_info`, `browser_interactive`, `browser_frames`,
|
|
417
|
-
`browser_accessibility_snapshot`, `browser_extract`,
|
|
427
|
+
`browser_accessibility_snapshot`, `browser_extract`,
|
|
418
428
|
`browser_find_text`, `browser_search_page`, `browser_find_elements`,
|
|
419
429
|
`browser_dropdown_options`, `browser_computed_style`, `browser_page_next`,
|
|
420
430
|
`browser_get_html`, `browser_search_network_log`, `browser_inspect_element`,
|
|
421
431
|
`browser_challenge`, `browser_doctor`, and `server_health`.
|
|
422
432
|
|
|
423
433
|
**Navigation and interaction:** `browser_navigate`, `browser_back`,
|
|
424
|
-
`
|
|
434
|
+
`browser_forward`, `browser_reload`, `browser_switch_tab`,
|
|
425
435
|
`browser_close_tab`, `browser_click`, `browser_input`, `browser_select`,
|
|
426
436
|
`browser_scroll`, `browser_scroll_to_bottom`, `browser_key`,
|
|
427
|
-
`browser_wait`, `browser_wait_for_element`, `browser_wait_for_text`,
|
|
428
|
-
`
|
|
429
|
-
`
|
|
437
|
+
`browser_wait`, `browser_wait_for_element`, `browser_wait_for_text`,
|
|
438
|
+
`browser_wait_for_url`, `browser_wait_for_network_idle`,
|
|
439
|
+
`browser_hover`, `browser_move`, `browser_press_and_hold`,
|
|
440
|
+
and `browser_close`.
|
|
430
441
|
|
|
431
442
|
**Available local capabilities:** `browser_screenshot`, `browser_pdf`,
|
|
432
443
|
`browser_upload`, `browser_downloads`, `browser_network_log`,
|
|
433
|
-
`
|
|
444
|
+
`browser_console_log`, `browser_resource_blocking`,
|
|
434
445
|
`browser_dialog`, `browser_cookies`, `browser_storage`,
|
|
435
|
-
`browser_batch`, `
|
|
436
|
-
|
|
446
|
+
`browser_batch`, `browser_solve_challenge`, `browser_wait_for_human`,
|
|
447
|
+
and all other browser tools are available by default.
|
|
437
448
|
`browser_close_session` remains a local lifecycle control and does not change
|
|
438
449
|
browser permissions. Page evaluation is available by default and can be
|
|
439
450
|
disabled explicitly with `SMOOTH_OPERATOR_ALLOW_EVAL=false`.
|
|
@@ -448,19 +459,20 @@ acquire a profile lease; the next browser operation retries acquisition.
|
|
|
448
459
|
`ready` reports readiness based on the runtime's current ownership state.
|
|
449
460
|
|
|
450
461
|
`browser_evaluate` is page JavaScript and is available by default (set
|
|
451
|
-
`SMOOTH_OPERATOR_ALLOW_EVAL=false` when it is not wanted). `
|
|
452
|
-
accepts only a
|
|
462
|
+
`SMOOTH_OPERATOR_ALLOW_EVAL=false` when it is not wanted). `browser_batch`
|
|
463
|
+
accepts only a validated array of browser actions; it is not a shell,
|
|
453
464
|
Python, or arbitrary code runner. Its explicit `evaluate` action still follows
|
|
454
|
-
the page-evaluation policy, and its optional `timeoutMs` is the
|
|
455
|
-
deadline
|
|
465
|
+
the page-evaluation policy, and its optional `timeoutMs` is the whole-batch
|
|
466
|
+
deadline (120,000 ms default, 600,000 ms maximum).
|
|
456
467
|
Destructive batch actions require explicit confirmation. There is no generic
|
|
457
468
|
arbitrary CDP command or host-code execution
|
|
458
469
|
tool. `browser_wait_for_human` pauses for an operator to complete a
|
|
459
|
-
visible sign-in or challenge.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
challenge absent; present and exhausted
|
|
463
|
-
budget and `automation_exhausted` is
|
|
470
|
+
visible sign-in or challenge. The connected harness loops
|
|
471
|
+
`browser_solve_challenge` (one evidence cycle per call) and, after
|
|
472
|
+
exhaustion, `browser_wait_for_human`. Success requires a fresh final
|
|
473
|
+
classification that reports the challenge absent; present and exhausted
|
|
474
|
+
cycles report the remaining attempt budget and `automation_exhausted` is
|
|
475
|
+
the final non-success state.
|
|
464
476
|
`browser_close_session`
|
|
465
477
|
closes the one native browser session by its explicit session identifier.
|
|
466
478
|
|
|
@@ -473,14 +485,19 @@ accepts the same option at the top level and captures only one snapshot after
|
|
|
473
485
|
the final action.
|
|
474
486
|
|
|
475
487
|
Element-targeting tools accept exactly one of `target`, `ref` (`e5` or
|
|
476
|
-
`ref:e5`), CSS `selector`, or zero-based `index
|
|
488
|
+
`ref:e5`), CSS `selector`, or zero-based `index`. They do not take
|
|
489
|
+
`operation`. `browser_click`, `browser_move`, and `browser_press_and_hold`
|
|
490
|
+
also accept `coordinateX`/`coordinateY`; hold also accepts start/end
|
|
491
|
+
coordinates or a path. `browser_challenge` is detect-only; solve is
|
|
492
|
+
`browser_solve_challenge`.
|
|
477
493
|
`browser_select` additionally requires exactly one of `optionValue` or
|
|
478
494
|
`optionValues`; `browser_upload` requires exactly one target form and exactly
|
|
479
495
|
one of `filePath` or `filePaths`. Snapshot refs and indexes are invalidated by
|
|
480
496
|
navigation and DOM-changing actions, so refresh the snapshot before reuse.
|
|
481
497
|
Prefer canonical tools (`browser_tabs`, `browser_snapshot`, `browser_input`,
|
|
482
|
-
`browser_back`, `browser_close`, `browser_extract`)
|
|
483
|
-
|
|
498
|
+
`browser_back`, `browser_close`, `browser_extract`). Compatibility aliases
|
|
499
|
+
and retired `browser_logs` are unlisted catalog maps; `tools/list` is exactly
|
|
500
|
+
the 57 names.
|
|
484
501
|
|
|
485
502
|
The current-page observation tools `browser_interactive`, `browser_frames`,
|
|
486
503
|
`browser_page_info`, and `browser_challenge` accept an optional `pageId`; omit
|
|
@@ -550,7 +567,8 @@ page. `browser_search_network_log` scans once, retains only the requested result
|
|
|
550
567
|
page, and filters metadata by request ID, URL, method, status, and resource
|
|
551
568
|
type. Headers, cookies, request
|
|
552
569
|
bodies, and response bodies are not returned; search terms and URLs are treated
|
|
553
|
-
as untrusted data and secret query values are redacted.
|
|
570
|
+
as untrusted data and secret query values are redacted. `browser_console_log` is
|
|
571
|
+
the bounded console journal.
|
|
554
572
|
|
|
555
573
|
`browser_resource_blocking` is page-scoped with strict `get`, `set`, and
|
|
556
574
|
`clear` operations. Its only selectable types are `image`, `stylesheet`,
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Fail closed. Bound and redact untrusted page, search, and log data. Dual-enforce
|
|
4
|
+
the same `SecurityPolicy` at the MCP edge and again at the browser/research
|
|
5
|
+
service.
|
|
6
|
+
|
|
7
|
+
## Network
|
|
8
|
+
|
|
9
|
+
- HTTP(S) only. Credentials in URLs are rejected. Untrusted URL credentials are redacted in output.
|
|
10
|
+
- Redirects are re-checked with the same policy and fail closed. Research fetch uses `redirect: "error"`.
|
|
11
|
+
- Private, link-local, and multicast addresses are blocked unless `SMOOTH_OPERATOR_ALLOW_PRIVATE_NETWORK=true`. Loopback is allowed.
|
|
12
|
+
- DNS is a best-effort preflight (deny-only cache). The browser resolver is not pinned.
|
|
13
|
+
- Resource blocking is page-scoped to image, stylesheet, font, media, and script. Document/navigation cannot be selected.
|
|
14
|
+
|
|
15
|
+
## Files
|
|
16
|
+
|
|
17
|
+
- Allowed roots are canonicalized once. Filesystem-root roots and regular-file roots are rejected.
|
|
18
|
+
- Unresolved symlink escapes fail closed. Blocked-path errors include only configured-root metadata.
|
|
19
|
+
- Uploads: at most 20 files, 50 MiB each, 100 MiB aggregate; every staging path is cleaned on all outcomes. Multiple files require `multiple` on the input.
|
|
20
|
+
- Config files and backups are bounded, owner-only (`chmod 600`), regular, and symlink-safe. Writes use a temp file, flush, then atomic rename.
|
|
21
|
+
|
|
22
|
+
## Page data
|
|
23
|
+
|
|
24
|
+
Returned HTML, text, accessibility, inspect, cookies, and storage omit:
|
|
25
|
+
|
|
26
|
+
- scripts and event-handler attributes
|
|
27
|
+
- form values (including accessibility descendants)
|
|
28
|
+
- cookie values on read
|
|
29
|
+
- secret placeholders, bearer-shaped strings, and secret query keys
|
|
30
|
+
|
|
31
|
+
Truncation is explicit (`truncated`, `mcpOutputTruncated`, `*Truncated`, omission counts). Silent omission is a bug.
|
|
32
|
+
|
|
33
|
+
Page evaluation is on by default and gated by `SecurityPolicy.assertEvalAllowed()` at the MCP edge and again in `BrowserService`. There is no generic CDP or host-code tool.
|
|
34
|
+
|
|
35
|
+
## HTTP
|
|
36
|
+
|
|
37
|
+
- Loopback bind unless remote HTTP is explicitly enabled.
|
|
38
|
+
- Remote HTTP requires a token of at least 32 printable ASCII characters.
|
|
39
|
+
- Host and Origin are parsed and allowlisted. Bearer comparison is constant-time.
|
|
40
|
+
- `healthz` uses the same Host/Origin/bearer policy as `/mcp`.
|
|
41
|
+
- Bodies are capped (default 2 MiB). Malformed or partial responses close the connection.
|
|
42
|
+
|
|
43
|
+
## Browser process
|
|
44
|
+
|
|
45
|
+
- One profile lease (`.smooth-operator-profile.lock`). Concurrent sessions fail with `BROWSER_PROFILE_IN_USE`.
|
|
46
|
+
- Managed and launch require a regular executable, a bounded 64 KiB `/json/version` probe, and target auto-attach acknowledgement.
|
|
47
|
+
- Connect mode attaches to the configured `browserURL` / `wsEndpoint`. The wizard Personal Chrome helper uses `~/.smooth-operator/personal-chrome` and does not take over daily Chrome. Advanced `chrome://inspect` attach to a daily profile is opt-in; see [mcp-server.md](mcp-server.md).
|
|
48
|
+
- After an uncooperative timeout the old lifecycle is retired before the queue advances. A late close cannot release another request's lock.
|
|
49
|
+
|
|
50
|
+
## Challenges and research
|
|
51
|
+
|
|
52
|
+
Challenges are classified from independently bounded title, text, HTML, frame, and visible-marker evidence. The connected harness loops `browser_solve_challenge` / `browser_wait_for_human`. Success requires a fresh classification that is explicitly **absent**. There is no solver, token injection, or anti-bot bypass. Identity stays native: launch args do not fabricate UA, platform, WebGL, canvas, client hints, or `navigator.webdriver`.
|
|
53
|
+
|
|
54
|
+
`web_search` is bounded DuckDuckGo HTML. Anti-bot responses are `SEARCH_BLOCKED`. The server does not complete challenges for the search provider.
|
|
55
|
+
|
|
56
|
+
## Logs
|
|
57
|
+
|
|
58
|
+
JSON lines on stderr. Secret keys and values are redacted. Logging failures never change protocol responses.
|
package/docs/tools.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Tools, resources, and prompts
|
|
2
|
+
|
|
3
|
+
The public surface is locked in `src/server/catalog.ts` and
|
|
4
|
+
`tests/contract-snapshot.test.ts`. `tools/list` is 57 one-job tools.
|
|
5
|
+
Compatibility aliases and retired `browser_logs` are unlisted catalog maps.
|
|
6
|
+
|
|
7
|
+
Operations: cookies `get`/`set`/`delete`; storage and resource-blocking
|
|
8
|
+
`get`/`set`/`clear`; network and console logs
|
|
9
|
+
`enable`/`disable`/`read`/`clear`/`read_and_clear`; dialogs
|
|
10
|
+
`get_text`/`accept`/`dismiss`/`send_keys`. Element tools take exactly one of
|
|
11
|
+
`target`, `ref`, `selector`, or `index` and have no `operation`.
|
|
12
|
+
`browser_click`, `browser_move`, and `browser_press_and_hold` also accept
|
|
13
|
+
`coordinateX`/`coordinateY` (hold also start/end coordinates or a path).
|
|
14
|
+
`browser_challenge` is detect-only; solve is `browser_solve_challenge`.
|
|
15
|
+
|
|
16
|
+
Canonical tools: `browser_tabs`, `browser_snapshot`, `browser_input`,
|
|
17
|
+
`browser_back`, `browser_close`, `browser_extract`, `browser_wait`,
|
|
18
|
+
`browser_network_log`, `browser_challenge`, `browser_batch`.
|
|
19
|
+
|
|
20
|
+
## Unlisted catalog maps
|
|
21
|
+
|
|
22
|
+
Compatibility aliases (`browser_list_tabs`, `browser_get_state`,
|
|
23
|
+
`browser_type`, `browser_extract_content`, `browser_go_back`,
|
|
24
|
+
`browser_close_all`, `browser_exec`) plus retired `browser_logs` are not in
|
|
25
|
+
`tools/list`.
|
|
26
|
+
|
|
27
|
+
| Unlisted name | Canonical call |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `browser_list_tabs` | `browser_tabs` |
|
|
30
|
+
| `browser_get_state` | `browser_snapshot` |
|
|
31
|
+
| `browser_type` | `browser_input` |
|
|
32
|
+
| `browser_extract_content` | `browser_extract` |
|
|
33
|
+
| `browser_go_back` | `browser_back` |
|
|
34
|
+
| `browser_close_all` | `browser_close` |
|
|
35
|
+
| `browser_exec` | `browser_batch` (`actions` is a validated array, never a shell) |
|
|
36
|
+
| `browser_logs` | `browser_network_log` `{ operation: "read" }` |
|
|
37
|
+
|
|
38
|
+
## Observation
|
|
39
|
+
|
|
40
|
+
`browser_snapshot`, `browser_tabs`, `browser_list_sessions`,
|
|
41
|
+
`browser_page_info`, `browser_interactive`, `browser_frames`,
|
|
42
|
+
`browser_accessibility_snapshot`, `browser_extract`,
|
|
43
|
+
`browser_find_text`, `browser_search_page`, `browser_find_elements`,
|
|
44
|
+
`browser_inspect_element`, `browser_dropdown_options`, `browser_computed_style`,
|
|
45
|
+
`browser_page_next`, `browser_get_html`, `browser_search_network_log`,
|
|
46
|
+
`browser_challenge`, `browser_doctor`, `server_health`.
|
|
47
|
+
|
|
48
|
+
Refs (`e5` / `ref:e5`), indexes, and coordinates are observation-bound. Refresh
|
|
49
|
+
them after navigation or DOM changes. Snapshot refs are page/frame/revision-bound.
|
|
50
|
+
|
|
51
|
+
## Navigation and interaction
|
|
52
|
+
|
|
53
|
+
`browser_navigate`, `browser_back`, `browser_forward`,
|
|
54
|
+
`browser_reload`, `browser_switch_tab`, `browser_close_tab`, `browser_click`,
|
|
55
|
+
`browser_input`, `browser_select`, `browser_scroll`, `browser_scroll_to_bottom`,
|
|
56
|
+
`browser_key`, `browser_wait`, `browser_wait_for_element`,
|
|
57
|
+
`browser_wait_for_text`, `browser_wait_for_url`, `browser_wait_for_network_idle`,
|
|
58
|
+
`browser_hover`, `browser_move`, `browser_press_and_hold`, `browser_close`.
|
|
59
|
+
|
|
60
|
+
Also: `browser_upload`, `browser_screenshot`, `browser_pdf`, `browser_downloads`,
|
|
61
|
+
`browser_evaluate`, `browser_batch`, `browser_dialog`,
|
|
62
|
+
`browser_cookies`, `browser_storage`, `browser_network_log`,
|
|
63
|
+
`browser_console_log`, `browser_resource_blocking`, `browser_solve_challenge`,
|
|
64
|
+
`browser_wait_for_human`, `web_search`, `browser_close_session`.
|
|
65
|
+
|
|
66
|
+
`includeSnapshot=true` on a mutation returns one trailing snapshot. Destructive
|
|
67
|
+
batches require `confirmDestructive=true`. `browser_batch` accepts validated JSON
|
|
68
|
+
actions only.
|
|
69
|
+
|
|
70
|
+
## Envelope
|
|
71
|
+
|
|
72
|
+
Success and error results set `content[0].text` to the JSON of `structuredContent`
|
|
73
|
+
(non-objects wrapped as `{ value }`). Errors are
|
|
74
|
+
`{ ok: false, error: { code, message, retryable, details?, recovery? } }`.
|
|
75
|
+
Truncation flags are explicit. Screenshot bytes travel as MCP image content,
|
|
76
|
+
not inside the JSON record.
|
|
77
|
+
|
|
78
|
+
## Resources
|
|
79
|
+
|
|
80
|
+
- `smooth-operator://server/capabilities`
|
|
81
|
+
- `smooth-operator://browser/tabs`
|
|
82
|
+
- `smooth-operator://browser/page/current`
|
|
83
|
+
- `smooth-operator://browser/page/{pageId}`
|
|
84
|
+
- `smooth-operator://browser/downloads`
|
|
85
|
+
- `smooth-operator://browser/logs/network`
|
|
86
|
+
- `smooth-operator://browser/logs/console`
|
|
87
|
+
|
|
88
|
+
## Prompts
|
|
89
|
+
|
|
90
|
+
`agent-chrome-setup`, `browser-workflow`, `extract-page`, `research-question`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smooth-operator-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "npm@12.0.2",
|
|
6
6
|
"description": "A lightweight, production-grade MCP server for secure browser automation.",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"docs/mcp-server.md",
|
|
15
15
|
"docs/harnesses.md",
|
|
16
16
|
"docs/STEALTH-GUIDE.md",
|
|
17
|
+
"docs/architecture.md",
|
|
18
|
+
"docs/security.md",
|
|
19
|
+
"docs/tools.md",
|
|
20
|
+
"docs/config.md",
|
|
17
21
|
"README.md",
|
|
18
22
|
"LICENSE",
|
|
19
23
|
".env.example"
|
|
@@ -28,13 +32,13 @@
|
|
|
28
32
|
"postbuild": "node scripts/set-executable.mjs dist/smooth-operator.mjs",
|
|
29
33
|
"lint": "eslint . --ignore-pattern scripts/benchmark-network-journal.ts",
|
|
30
34
|
"typecheck": "tsc --noEmit",
|
|
31
|
-
"test": "vitest run",
|
|
35
|
+
"test": "vitest run --exclude tests/browser-live.test.ts",
|
|
32
36
|
"test:unit": "vitest run --exclude tests/browser-live.test.ts",
|
|
33
37
|
"benchmark:network": "tsx scripts/benchmark-network-journal.ts",
|
|
34
38
|
"test:browser:live": "node scripts/test-browser-live.mjs",
|
|
35
|
-
"test:watch": "vitest",
|
|
36
|
-
"test:coverage": "vitest run --coverage",
|
|
37
|
-
"verify": "npm run lint && npm run typecheck && npm test && npm run dead-code && npm run package:smoke",
|
|
39
|
+
"test:watch": "vitest --exclude tests/browser-live.test.ts",
|
|
40
|
+
"test:coverage": "vitest run --coverage --exclude tests/browser-live.test.ts",
|
|
41
|
+
"verify": "npm run lint && npm run typecheck && npm run test:coverage && npm run dead-code && npm run package:smoke",
|
|
38
42
|
"dead-code": "knip --include files,exports,dependencies,unlisted --no-progress",
|
|
39
43
|
"mcp:stdio": "npm run start -- --transport stdio",
|
|
40
44
|
"mcp:http": "npm run start -- --transport http",
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
"@modelcontextprotocol/server": "2.0.0",
|
|
50
54
|
"ghost-cursor": "1.4.2",
|
|
51
55
|
"puppeteer-core": "25.10.0",
|
|
52
|
-
"zod": "4.
|
|
56
|
+
"zod": "4.6.2"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@modelcontextprotocol/client": "^2.0.0",
|