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.
@@ -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,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-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
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 an internal connected-AI loop: each call is one
60
- bounded verification cycle. It collects a fresh challenge classification and
61
- bounded visual/state evidence, including `attemptsRemaining`; the connected AI
62
- uses normal browser actions and calls it again until the final classification
63
- explicitly reports the challenge absent or `automation_exhausted`. The tool
64
- returns screenshot data as MCP image content when requested. `present`,
65
- `unknown`, or a failed probe is never success, and human handoff is only an
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 64-tool registry is
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
@@ -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
 
@@ -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` | Native-identity viewport compatibility script |
330
- | `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 |
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; use the internal AI workflow or human handoff
363
- 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.
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-AI
399
- challenge loop collects fresh classification and visual/state evidence,
400
- 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.
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 64 public tools in these groups. Every input is schema-validated;
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
- `browser_list_tabs`, `browser_list_sessions`, `browser_get_state`,
425
+ `browser_list_sessions`,
416
426
  `browser_page_info`, `browser_interactive`, `browser_frames`,
417
- `browser_accessibility_snapshot`, `browser_extract`, `browser_extract_content`,
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
- `browser_go_back`, `browser_forward`, `browser_reload`, `browser_switch_tab`,
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`, `browser_wait_for_url`,
428
- `browser_wait_for_network_idle`, `browser_hover`, `browser_move`, `browser_press_and_hold`,
429
- `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`.
430
441
 
431
442
  **Available local capabilities:** `browser_screenshot`, `browser_pdf`,
432
443
  `browser_upload`, `browser_downloads`, `browser_network_log`,
433
- `browser_search_network_log`, `browser_resource_blocking`, `browser_console_log`,
444
+ `browser_console_log`, `browser_resource_blocking`,
434
445
  `browser_dialog`, `browser_cookies`, `browser_storage`,
435
- `browser_batch`, `browser_exec`, `browser_wait_for_human`,
436
- `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.
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). `browser_exec`
452
- 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,
453
464
  Python, or arbitrary code runner. Its explicit `evaluate` action still follows
454
- the page-evaluation policy, and its optional `timeoutMs` is the same whole-batch
455
- deadline as `browser_batch` (120,000 ms default, 600,000 ms maximum).
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. `browser_solve_challenge` is an internal
460
- connected-AI observe/act/verify loop: it returns bounded evidence and is
461
- successful only when a fresh final classification explicitly reports the
462
- challenge absent; present and exhausted cycles report the remaining attempt
463
- 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.
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`, plus the operation fields.
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`); browser-use compatibility
483
- aliases are retained and labeled in `tools/list`.
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`,
@@ -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.2.0",
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.5.4"
56
+ "zod": "4.6.2"
53
57
  },
54
58
  "devDependencies": {
55
59
  "@modelcontextprotocol/client": "^2.0.0",