smooth-operator-mcp 3.1.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.
@@ -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`; connected mode launches and attaches
14
- to a dedicated debugging profile and does not claim ownership of an operator's
15
- daily browser. A profile is not a privacy boundary from the websites it visits,
16
- so use a dedicated profile and the narrowest policy that fits the task.
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 setting.
21
- It applies only an explicitly configured viewport and never hides automation
22
- signals or fabricates a user agent, platform, browser version, language, client
23
- hints, WebGL, canvas, TLS, or operating-system identity. Set it to `false` to
24
- skip that viewport script entirely.
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 for the same supported patch set. `SMOOTH_OPERATOR_STEALTH_GPU=true`
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, typing, and
31
- scrolling timing wrappers. It defaults off for the fastest raw interaction
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,22 +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-AI challenge loop
59
+ ## Connected-harness challenge loop
55
60
 
56
61
  `browser_challenge` is an evidence-only detector and is available by default.
57
- `browser_solve_challenge` is an internal connected-AI loop: each call is one
58
- bounded verification cycle. It collects a fresh challenge classification and
59
- bounded visual/state evidence, including `attemptsRemaining`; the connected AI
60
- uses normal browser actions and calls it again until the final classification
61
- explicitly reports the challenge absent or `automation_exhausted`. The tool
62
- returns screenshot data as MCP image content when requested. `present`,
63
- `unknown`, or a failed probe is never success, and human handoff is only an
62
+ Title, text, HTML, frame-source, and visible-marker inputs have independent
63
+ limits so one oversized evidence category cannot hide later challenge markers.
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
64
71
  explicit option after exhaustion.
65
72
 
66
73
  `browser_wait_for_human` is an optional handoff for a person to complete a
67
74
  visible challenge or sign-in step. It does not claim success without a fresh
68
75
  final classification. The server does not rotate identities or open network,
69
- 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.
70
80
 
71
81
  ## Boundaries and responsible use
72
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Interactive wizard (directly to your harness)
4
4
 
5
- `smooth-operator install <harness>` is interactive by default — it asks exactly 3
5
+ `smooth-operator install <harness>` is interactive by default — it asks exactly three
6
6
  curated questions (profile ownership, headed/headless display, and browser
7
7
  executable) with recommended defaults in brackets.
8
8
  Omitting `<harness>` is allowed too: on a TTY the installer prompts for the
@@ -21,7 +21,9 @@ When you pick “connected browser” (mode `connect`), the wizard finds Chromiu
21
21
  `discovery.ts`, launches a dedicated debugging profile under
22
22
  `~/.smooth-operator/personal-chrome` with port `9222`, and probes the loopback
23
23
  endpoint until a valid DevTools version response is live (33 attempts within a
24
- default 10-second deadline). On success it writes
24
+ default 10-second deadline). If launch or readiness fails, the helper terminates
25
+ the child process on a best-effort basis instead of leaving it detached. On
26
+ success it writes
25
27
  `SMOOTH_OPERATOR_BROWSER_MODE=connect` and
26
28
  `SMOOTH_OPERATOR_BROWSER_URL=http://127.0.0.1:9222` for you. This does not
27
29
  attach to or take ownership of an operator's daily browser profile. No manual
@@ -56,7 +58,7 @@ npm install -g .
56
58
  smooth-operator install --help
57
59
  ```
58
60
 
59
- After installation, restart the harness and confirm the 64-tool registry is
61
+ After installation, restart the harness and confirm the 57-tool registry is
60
62
  visible. `server_health` reports the active capabilities; `browser_doctor`
61
63
  reports whether the configured or discovered executable is launch-ready and
62
64
  whether a managed DevTools endpoint is live. Optional idle cleanup is disabled
@@ -159,7 +161,8 @@ legacy shape, `enabled` defaults to `true`; `enabled: false` is likewise an
159
161
  explicit conflict. If it has malformed `mcp` or malformed `mcp.servers`,
160
162
  installation fails closed rather than replacing user data.
161
163
  Comments and trailing commas are accepted as JSONC; a successful update writes
162
- normalized JSON and creates a unique owner-only backup first.
164
+ normalized JSON through an owner-only temporary file, flushes it before atomic
165
+ replacement, and creates a unique owner-only backup first.
163
166
 
164
167
  After editing, run `opencode mcp list` or restart OpenCode. OpenCode also
165
168
  supports adding the server interactively with `opencode mcp add`; that is the
@@ -239,7 +242,8 @@ Official reference: <https://code.visualstudio.com/docs/copilot/chat/mcp-servers
239
242
  ## Cursor, Windsurf, and Claude Desktop
240
243
 
241
244
  These clients use JSON configuration files, so the installer performs a
242
- careful merge and writes atomically.
245
+ careful merge and writes through a flushed owner-only temporary file before
246
+ atomic replacement.
243
247
 
244
248
  | Target | Default path |
245
249
  | --- | --- |
@@ -25,11 +25,15 @@ MCP client
25
25
  └─ Logger and safe error boundary
26
26
  ```
27
27
 
28
- `src/server/main.ts` owns transport startup, authentication, signal handling,
29
- and graceful shutdown. `src/server/mcp.ts` registers the public protocol
30
- surface. `src/server/runtime.ts` owns dependency lifecycle. Browser operations
31
- are in `src/server/browser/service.ts`; policy and configuration are in
32
- `src/server/policy.ts` and `src/server/config.ts`.
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
 
@@ -43,15 +47,14 @@ smooth-operator --help
43
47
 
44
48
  (The registry package is `smooth-operator-mcp`; plain `smooth-operator` is an unrelated library. You can also install straight from GitHub: `npm install -g github:Gitshop77/Smooth-Operator`.)
45
49
 
46
- The interactive installer asks exactly three questions: (1) browser profile
47
- ownership, (2) headed or headless display, and (3) which Chromium executable to
48
- use. Its recommended defaults are a managed private persistent profile, headed
50
+ The interactive installer asks exactly three questions: browser profile
51
+ ownership, headed or headless display, and which Chromium executable to use.
52
+ Its recommended defaults are a managed private persistent profile, headed
49
53
  display, and the first detected Chromium executable. It also enables page eval
50
54
  and the identity-preserving compatibility profile; behavioral timing is off
51
55
  for fast deterministic input and can be enabled explicitly. Managed mode owns
52
- its profile. Connected mode launches and attaches to
53
- a dedicated debugging profile and does not claim ownership of an operator's
54
- daily browser.
56
+ its profile. Connected mode launches and attaches to a dedicated debugging
57
+ profile and does not claim ownership of an operator's daily browser.
55
58
 
56
59
  From a checkout:
57
60
 
@@ -132,7 +135,10 @@ and Origin validation and include `Authorization: Bearer <token>`. The token
132
135
  is compared in constant time. Request bodies are bounded to 2,000,000 bytes by
133
136
  default and concurrent requests are capped. The process drains in-flight work
134
137
  for a short bounded period on SIGINT/SIGTERM, then closes the MCP handler,
135
- browser, and HTTP server.
138
+ browser, and HTTP server. Native JSON rejections use no-store and nosniff
139
+ headers; overload responses also include `Retry-After: 1`. Preflight responses
140
+ advertise a fixed request-header allowlist rather than reflecting arbitrary
141
+ requested names.
136
142
 
137
143
  Remote binding is deliberately guarded:
138
144
 
@@ -163,7 +169,8 @@ launches it with `${SMOOTH_OPERATOR_DATA_DIR}/browser` as a non-default profile,
163
169
  and records its loopback DevTools endpoint for later reattachment. Sign in once
164
170
  in the visible window; its sessions persist in that private profile. The
165
171
  `browser_doctor` tool reports executable resolution and endpoint state without
166
- evaluating page content.
172
+ evaluating page content. Failed personal-Chrome helper launches are terminated
173
+ on a best-effort basis.
167
174
 
168
175
  The managed browser is headed by default for sign-in and human handoff. On CI or a
169
176
  displayless host, explicitly set `SMOOTH_OPERATOR_BROWSER_HEADLESS=true` or use
@@ -323,8 +330,8 @@ variables include:
323
330
  | `SMOOTH_OPERATOR_ALLOWED_FILE_ROOTS` | data `files`, `downloads` | Explicit roots replace defaults |
324
331
  | `SMOOTH_OPERATOR_ALLOW_PRIVATE_NETWORK` | `false` | Allows non-loopback private targets when true |
325
332
  | `SMOOTH_OPERATOR_ALLOW_EVAL` | `true` | Set `false` to disable page JavaScript |
326
- | `SMOOTH_OPERATOR_STEALTH_ENABLED` | `true` | Native-identity viewport compatibility script |
327
- | `SMOOTH_OPERATOR_STEALTH_PROFILE` | `balanced` | `balanced` or `max` compatibility label |
333
+ | `SMOOTH_OPERATOR_STEALTH_ENABLED` | `true` | Compatibility label; identity stays native |
334
+ | `SMOOTH_OPERATOR_STEALTH_PROFILE` | `balanced` | `balanced` or `max`; no patch-set difference |
328
335
  | `SMOOTH_OPERATOR_STEALTH_GPU` | `false` | Adds opt-in GPU launch flags |
329
336
  | `SMOOTH_OPERATOR_BEHAVIOR_ENABLED` | `false` | Opt-in timing wrappers |
330
337
  | `SMOOTH_OPERATOR_HTTP_HOST` | `127.0.0.1` | HTTP bind host |
@@ -356,8 +363,9 @@ loop is introduced.
356
363
  Raw MCP/tool-call speed is not the main bot-detection vector. Sites can score
357
364
  network and browser identity, IP reputation, session history, and interaction
358
365
  timing independently. A faster call does not bypass a challenge or make an
359
- automated session legitimate; use the internal AI workflow or human handoff
360
- only where the target permits automation.
366
+ automated session legitimate. The connected harness loops
367
+ `browser_solve_challenge` / `browser_wait_for_human` only where the target
368
+ permits automation.
361
369
 
362
370
  ## Security enforcement layers
363
371
 
@@ -392,9 +400,10 @@ are always applied, with explicit opt-ins where documented:
392
400
  - Page text, HTML, titles, attributes, search snippets, cookies, and logs are
393
401
  treated as untrusted data, normalized, bounded, and redacted before output.
394
402
  - Challenge and anti-bot markers are reported from bounded evidence. The server
395
- does not rotate identities or silently bypass challenges. The connected-AI
396
- challenge loop collects fresh classification and visual/state evidence,
397
- allows ordinary browser actions, and verifies with a subsequent call.
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.
398
407
 
399
408
  Run the server with a dedicated browser profile and the smallest domain and
400
409
  file-root allowlists that fit the task. Browser automation can still perform
@@ -405,32 +414,37 @@ for confirming destructive calls.
405
414
 
406
415
  ### Tools
407
416
 
408
- The registry exposes 64 public tools in these groups. Every input is schema-validated;
409
- 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`.
410
423
 
411
424
  **Observation and extraction:** `browser_snapshot`, `browser_tabs`,
412
- `browser_list_tabs`, `browser_list_sessions`, `browser_get_state`,
425
+ `browser_list_sessions`,
413
426
  `browser_page_info`, `browser_interactive`, `browser_frames`,
414
- `browser_accessibility_snapshot`, `browser_extract`, `browser_extract_content`,
427
+ `browser_accessibility_snapshot`, `browser_extract`,
415
428
  `browser_find_text`, `browser_search_page`, `browser_find_elements`,
416
429
  `browser_dropdown_options`, `browser_computed_style`, `browser_page_next`,
417
430
  `browser_get_html`, `browser_search_network_log`, `browser_inspect_element`,
418
431
  `browser_challenge`, `browser_doctor`, and `server_health`.
419
432
 
420
433
  **Navigation and interaction:** `browser_navigate`, `browser_back`,
421
- `browser_go_back`, `browser_forward`, `browser_reload`, `browser_switch_tab`,
434
+ `browser_forward`, `browser_reload`, `browser_switch_tab`,
422
435
  `browser_close_tab`, `browser_click`, `browser_input`, `browser_select`,
423
436
  `browser_scroll`, `browser_scroll_to_bottom`, `browser_key`,
424
- `browser_wait`, `browser_wait_for_element`, `browser_wait_for_text`, `browser_wait_for_url`,
425
- `browser_wait_for_network_idle`, `browser_hover`, `browser_move`, `browser_press_and_hold`,
426
- `browser_type`, `browser_close`, and `browser_close_all`.
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`.
427
441
 
428
442
  **Available local capabilities:** `browser_screenshot`, `browser_pdf`,
429
443
  `browser_upload`, `browser_downloads`, `browser_network_log`,
430
- `browser_search_network_log`, `browser_resource_blocking`, `browser_console_log`,
444
+ `browser_console_log`, `browser_resource_blocking`,
431
445
  `browser_dialog`, `browser_cookies`, `browser_storage`,
432
- `browser_batch`, `browser_exec`, `browser_wait_for_human`,
433
- `browser_solve_challenge`, and all other browser tools are available by default.
446
+ `browser_batch`, `browser_solve_challenge`, `browser_wait_for_human`,
447
+ and all other browser tools are available by default.
434
448
  `browser_close_session` remains a local lifecycle control and does not change
435
449
  browser permissions. Page evaluation is available by default and can be
436
450
  disabled explicitly with `SMOOTH_OPERATOR_ALLOW_EVAL=false`.
@@ -445,17 +459,20 @@ acquire a profile lease; the next browser operation retries acquisition.
445
459
  `ready` reports readiness based on the runtime's current ownership state.
446
460
 
447
461
  `browser_evaluate` is page JavaScript and is available by default (set
448
- `SMOOTH_OPERATOR_ALLOW_EVAL=false` when it is not wanted). `browser_exec`
449
- accepts only a JSON array of validated browser actions; it is not a shell,
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,
450
464
  Python, or arbitrary code runner. Its explicit `evaluate` action still follows
451
- the page-evaluation policy. Destructive batch actions require explicit
452
- confirmation. There is no generic arbitrary CDP command or host-code execution
465
+ the page-evaluation policy, and its optional `timeoutMs` is the whole-batch
466
+ deadline (120,000 ms default, 600,000 ms maximum).
467
+ Destructive batch actions require explicit confirmation. There is no generic
468
+ arbitrary CDP command or host-code execution
453
469
  tool. `browser_wait_for_human` pauses for an operator to complete a
454
- visible sign-in or challenge. `browser_solve_challenge` is an internal
455
- connected-AI observe/act/verify loop: it returns bounded evidence and is
456
- successful only when a fresh final classification explicitly reports the
457
- challenge absent; present and exhausted cycles report the remaining attempt
458
- budget and `automation_exhausted` is the final non-success state.
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.
459
476
  `browser_close_session`
460
477
  closes the one native browser session by its explicit session identifier.
461
478
 
@@ -467,6 +484,21 @@ revision. A snapshot failure is reported as `snapshot: null` with a bounded
467
484
  accepts the same option at the top level and captures only one snapshot after
468
485
  the final action.
469
486
 
487
+ Element-targeting tools accept exactly one of `target`, `ref` (`e5` or
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`.
493
+ `browser_select` additionally requires exactly one of `optionValue` or
494
+ `optionValues`; `browser_upload` requires exactly one target form and exactly
495
+ one of `filePath` or `filePaths`. Snapshot refs and indexes are invalidated by
496
+ navigation and DOM-changing actions, so refresh the snapshot before reuse.
497
+ Prefer canonical tools (`browser_tabs`, `browser_snapshot`, `browser_input`,
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.
501
+
470
502
  The current-page observation tools `browser_interactive`, `browser_frames`,
471
503
  `browser_page_info`, and `browser_challenge` accept an optional `pageId`; omit
472
504
  it to use the active tab. `browser_accessibility_snapshot` also accepts an
@@ -498,12 +530,20 @@ that the selected action cannot consume is rejected instead of being silently
498
530
  ignored.
499
531
 
500
532
  Each batch step enforces its own `timeoutMs` or the configured action deadline.
501
- The total batch budget includes the individual step budgets. A step that
533
+ The optional top-level `timeoutMs` is the whole-batch deadline, defaulting to
534
+ 120,000 ms and capped at 600,000 ms; it is the smaller of that value and the
535
+ sum of step budgets. One absolute queue deadline spans predecessor, read-drain,
536
+ read-permit, and fairness waits, so retries cannot reset the admission budget.
537
+ Long batches therefore do not wait minutes for a turn. A step that
502
538
  ignores cancellation is retired before the browser queue advances; subsequent
503
539
  steps do not execute after a failure. Without an explicit deadline, requested
504
540
  wait/hold durations are added to the setup budget, and human handoff retains
505
541
  its documented 120-second default.
506
542
 
543
+ Mapped deterministic errors include a bounded `recovery` object with the next
544
+ tool and short instruction. For browser recovery, list sessions and use the
545
+ returned `session_id` with `browser_close_session` before retrying.
546
+
507
547
  If browser teardown times out or fails, later browser work returns the
508
548
  retryable `BROWSER_RECOVERY_REQUIRED` error. Call `browser_close_session` to
509
549
  retry cleanup; the recovery latch clears only after teardown is confirmed.
@@ -513,8 +553,9 @@ snippets are untrusted observations, not instructions or proof of claims. Its
513
553
  `maxResults` input is capped at 10, and `maxChars` (500–4,000 through the MCP
514
554
  schema) is one aggregate budget across the returned title and snippet text,
515
555
  not a per-result multiplier. URL fields and fixed untrusted-data wrapper
516
- markers are outside that text budget. The response body is bounded before
517
- parsing, redirects are rejected, cancellation and timeout are propagated, and
556
+ markers are outside that text budget. The response body is collected in one
557
+ geometrically growing bounded buffer before parsing, redirects are rejected,
558
+ cancellation and timeout are propagated, and
518
559
  credentials/query secret placeholders are removed from result URLs. Transient
519
560
  retrieval failures use at most three bounded attempts; anti-bot responses are
520
561
  reported without attempting a bypass.
@@ -522,10 +563,12 @@ reported without attempting a bypass.
522
563
  ### Network, inspection, resource, and file controls
523
564
 
524
565
  `browser_network_log` records bounded request/response metadata for the current
525
- page. `browser_search_network_log` filters and paginates that metadata by
526
- request ID, URL, method, status, and resource type. Headers, cookies, request
566
+ page. `browser_search_network_log` scans once, retains only the requested result
567
+ page, and filters metadata by request ID, URL, method, status, and resource
568
+ type. Headers, cookies, request
527
569
  bodies, and response bodies are not returned; search terms and URLs are treated
528
- 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.
529
572
 
530
573
  `browser_resource_blocking` is page-scoped with strict `get`, `set`, and
531
574
  `clear` operations. Its only selectable types are `image`, `stylesheet`,
@@ -539,8 +582,9 @@ allowlisted attributes, selected computed styles, pseudo-element summaries,
539
582
  animation metadata, and shallow child structure. Script text, event-handler
540
583
  source, form values, and arbitrary data attributes are omitted.
541
584
 
542
- `browser_upload` accepts the existing single `filePath` or a `filePaths` array
543
- of up to 20 paths, but not both. Every source must pass the allowed-root,
585
+ `browser_upload` accepts `target`, `ref`, `selector`, or `index`, plus the
586
+ existing single `filePath` or a `filePaths` array of up to 20 paths, but not
587
+ both. Every source must pass the allowed-root,
544
588
  no-follow, and file-identity checks. Each file is at most 50 MiB and the
545
589
  aggregate is at most 100 MiB; multiple paths require a target input with the
546
590
  `multiple` attribute. Sources are staged sequentially and every staging path is
@@ -567,7 +611,7 @@ Resource output is bounded and follows the same redaction and policy rules as
567
611
  tool output. The capabilities resource also reports the native defaults and
568
612
  effective feature flags for local browser tools, page evaluation, stealth, and
569
613
  behavioral timing. Its `limits` metadata publishes the page-text, action-plan,
570
- research, and upload budgets used by the public boundary. Its challenge
614
+ batch-timeout, research, and upload budgets used by the public boundary. Its challenge
571
615
  metadata includes the default and maximum connected-AI attempt budgets and
572
616
  states that success requires an explicit absent classification.
573
617
 
@@ -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.