machine-bridge-mcp 0.12.2 → 0.14.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/CHANGELOG.md +38 -0
- package/README.md +44 -13
- package/SECURITY.md +7 -3
- package/browser-extension/devtools-input.js +136 -0
- package/browser-extension/manifest.json +3 -2
- package/browser-extension/page-automation.js +309 -29
- package/browser-extension/service-worker.js +108 -5
- package/docs/AGENT_CONTEXT.md +16 -6
- package/docs/ARCHITECTURE.md +7 -5
- package/docs/AUDIT.md +13 -1
- package/docs/CLIENTS.md +1 -1
- package/docs/ENGINEERING.md +1 -0
- package/docs/LOCAL_AUTOMATION.md +14 -10
- package/docs/LOGGING.md +3 -1
- package/docs/OPERATIONS.md +17 -5
- package/docs/TESTING.md +8 -7
- package/package.json +10 -4
- package/src/local/agent-context.mjs +76 -19
- package/src/local/app-automation.mjs +15 -1
- package/src/local/browser-bridge.mjs +186 -88
- package/src/local/browser-command.mjs +126 -0
- package/src/local/capability-observer.mjs +56 -0
- package/src/local/cli.mjs +21 -3
- package/src/local/default-instructions.mjs +21 -91
- package/src/local/network-proxy.mjs +34 -0
- package/src/local/process-sessions.mjs +7 -0
- package/src/local/project-package.mjs +234 -0
- package/src/local/relay-connection.mjs +26 -0
- package/src/local/runtime.mjs +23 -20
- package/src/shared/tool-catalog.json +212 -6
- package/src/worker/index.ts +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 - 2026-07-13
|
|
4
|
+
|
|
5
|
+
### Windows installation and OAuth callback reliability
|
|
6
|
+
|
|
7
|
+
- Bootstrap installation through pinned npm 12.0.1 from an empty temporary directory before using `--allow-scripts`, declare the npm 12 requirement in package `engines`, and make `machine-mcp doctor` verify the active npm version. This removes the unsupported old-npm path shown by Windows and gives accurate diagnostics for `Unknown cli config "--allow-scripts"` and legacy `devEngines.node` errors without guessing which package supplied the invalid metadata.
|
|
8
|
+
- Construct OAuth callback destinations with the URL API and return `303 See Other` after consent instead of manually concatenating a `Location` string. Add an end-to-end ChatGPT callback regression with reserved state characters and exact origin/path checks.
|
|
9
|
+
- Extend the isolated install smoke test to require npm 12, install from an empty directory, verify the packaged npm engine requirement, and run the test on Linux, macOS, and Windows CI.
|
|
10
|
+
|
|
11
|
+
### Existing-profile browser automation
|
|
12
|
+
|
|
13
|
+
- Keep the authenticated Manifest V3 extension as the primary browser backend so automation operates the user's ordinary Chromium profile, tabs, extensions, cookies, and login state instead of launching a separate profile. Add tab creation/activation/closure and explicit combined waits for URL, document readiness, page text, and element state.
|
|
14
|
+
- Replace fragile inspect-then-selector flows with bounded semantic snapshots containing stable per-document/frame element references, visibility/enabled/editable state, and viewport geometry. Page actions now wait for attachment, visibility, enabled/editable state, geometric stability, and unobscured pointer hit targets; ambiguous selectors and stale references fail explicitly.
|
|
15
|
+
- Add double-click, hover, append-text, and scroll-into-view actions. Top-frame click, double-click, hover, key press, and text input can use fixed short-lived Chromium DevTools Input commands through explicit `auto`, `trusted`, or `dom` modes. The extension exposes no caller-selected CDP method or arbitrary JavaScript and detaches the debugger in `finally`.
|
|
16
|
+
|
|
17
|
+
### Architecture, security, tests, and documentation
|
|
18
|
+
|
|
19
|
+
- Extract browser command normalization into a focused local module and isolate trusted input in a fixed extension module. Add a versioned extension capability handshake, stale-build reload guidance, explicit keepalive handling, and replacement validation that preserves the current compatible connection until the candidate is accepted; accepted replacements reject in-flight direct and proxied requests with retry guidance instead of leaving them to time out. Preserve resource-backed secrets/files, strict action/value validation, bounded broker messages, cancellation, source limits, and the existing owner-only pairing model.
|
|
20
|
+
- Add behavior-level tests for command contracts, trusted-input command sequences and cleanup, semantic-ref stability, deterministic scrolling, obscured-target waiting, stale refs, broker routing, catalog parity, and architecture invariants. Update browser setup, permission, security, architecture, testing, and tool documentation.
|
|
21
|
+
|
|
22
|
+
## 0.13.0 - 2026-07-13
|
|
23
|
+
|
|
24
|
+
### Automatic capability routing and observability
|
|
25
|
+
|
|
26
|
+
- Register bounded `package.*` commands from safe root `package.json` script names, while preserving explicit manifest override/deletion and never injecting script bodies. Windows uses a fixed `cmd.exe` wrapper for package-manager shims; Unix keeps direct executable argv. Extend default skill discovery to project `.codex/skills` and unrestricted `CODEX_HOME/skills` compatibility roots.
|
|
27
|
+
- Match installed applications by their actual names for every canonical-full task instead of requiring generic “app/window” words, with a bounded discovery cache to avoid repeated filesystem scans.
|
|
28
|
+
- Normalize a bounded set of common English inflections and Chinese workflow intents before skill/command ranking, and weight capability-name matches above incidental description overlap. This fixes Chinese selection of `skill-creator`, `smart-search-cli`, and `skill-installer` and prevents generic “create” wording from preferring unrelated design skills.
|
|
29
|
+
- Record privacy-preserving bootstrap and task-resolution telemetry in `server_info` and `project_overview`: counts, timestamps, source/load flags, selected capability metadata, and a runtime-keyed task fingerprint rather than raw task text. Suppress weak skill-overlap recommendations and clarify that the MCP host still controls whether the resolver and recommended tools are invoked.
|
|
30
|
+
|
|
31
|
+
### Process and network lifecycle
|
|
32
|
+
|
|
33
|
+
- Consolidate graceful process-tree termination plus forced escalation. Timeout, cancellation, and replacement now retain the escalation timer after the direct child exits, preventing a SIGTERM-resistant descendant with detached stdio from surviving as an orphan.
|
|
34
|
+
- Add relay support for standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` routing through a reviewed HTTP(S) proxy agent. Invalid proxy URLs or unsupported proxy protocols fail fast with corrective guidance; status exposes only direct/proxy/invalid route state and never proxy URLs or credentials.
|
|
35
|
+
|
|
36
|
+
### Architecture, tests, and documentation
|
|
37
|
+
|
|
38
|
+
- Extract package metadata/command discovery and capability observation into focused modules, add domain-to-adapter import-boundary checks, remove duplicate object fields, and centralize reused metadata parsing.
|
|
39
|
+
- Add behavior-level regression coverage for automatic package commands, direct `.codex/skills` compatibility, capability telemetry privacy, application-cache refresh, proxy selection/failure, and the direct-child-exits-first orphan-process boundary. Synchronize architecture, operations, logging, security, testing, and agent-context documentation.
|
|
40
|
+
|
|
3
41
|
## 0.12.2 - 2026-07-12
|
|
4
42
|
|
|
5
43
|
### Cross-platform persistence and browser reliability
|
package/README.md
CHANGED
|
@@ -47,10 +47,38 @@ This default prioritizes usability, not least privilege. `run_process` and proce
|
|
|
47
47
|
|
|
48
48
|
Node.js 26 or newer and npm 12 or newer are required. The repository pins the active development versions in `.node-version`, `.nvmrc`, and `packageManager`; project installs fail on older Node runtimes.
|
|
49
49
|
|
|
50
|
+
Use a new temporary directory for the bootstrap. The existing `npm`/`npx` front-end may inspect nearby project metadata before it launches the requested npm version; starting from an empty directory prevents an unrelated legacy `devEngines` declaration from blocking the bootstrap. The actual installation is then executed by the pinned npm 12 CLI rather than whichever npm happens to be first on `PATH`.
|
|
51
|
+
|
|
52
|
+
macOS/Linux:
|
|
53
|
+
|
|
50
54
|
```sh
|
|
51
|
-
|
|
55
|
+
install_dir="$(mktemp -d)"
|
|
56
|
+
(
|
|
57
|
+
cd "$install_dir"
|
|
58
|
+
npx --yes npm@12.0.1 install --global npm@12.0.1
|
|
59
|
+
npx --yes npm@12.0.1 install --global --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
|
|
60
|
+
)
|
|
61
|
+
rm -rf "$install_dir"
|
|
62
|
+
npm --version
|
|
63
|
+
machine-mcp doctor
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Windows Command Prompt (`cmd.exe`):
|
|
67
|
+
|
|
68
|
+
```bat
|
|
69
|
+
set "MBM_INSTALL_DIR=%TEMP%\machine-bridge-mcp-install-%RANDOM%-%RANDOM%"
|
|
70
|
+
mkdir "%MBM_INSTALL_DIR%"
|
|
71
|
+
pushd "%MBM_INSTALL_DIR%"
|
|
72
|
+
npx --yes npm@12.0.1 install --global npm@12.0.1
|
|
73
|
+
npx --yes npm@12.0.1 install --global --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
|
|
74
|
+
popd
|
|
75
|
+
rmdir /s /q "%MBM_INSTALL_DIR%"
|
|
76
|
+
npm --version
|
|
77
|
+
machine-mcp doctor
|
|
52
78
|
```
|
|
53
79
|
|
|
80
|
+
`Unknown cli config "--allow-scripts"` proves the Machine Bridge install was executed by npm 11 or older rather than the required npm 12. `Invalid property "node"` or `Invalid property "devEngines.node"` means that npm parsed a legacy `devEngines` object; the npm debug log is required to identify which package supplied it. The empty-directory, pinned-npm procedure avoids relying on that old parser. If `npm --version` still reports an older version after the bootstrap, reopen the terminal and rerun `machine-mcp doctor`.
|
|
81
|
+
|
|
54
82
|
Recent npm releases may otherwise warn that Wrangler's native dependencies (`esbuild`, `workerd`, and `sharp`) have install scripts awaiting approval. The scoped command approves the reviewed native script names that npm 12 evaluates during global resolution while `--omit=optional` keeps optional `fsevents` out of the installed runtime. `fsevents` is used for development-time filesystem watching rather than Machine Bridge runtime or deployment. Omitting `--omit=optional` can therefore produce a harmless blocked-script warning for `fsevents@2.3.3`; use the documented command rather than changing global npm policy. `machine-mcp doctor` remains the authoritative runtime check.
|
|
55
83
|
|
|
56
84
|
From a source checkout, the checked-in exact-version `allowScripts` policy approves the reviewed native dependencies:
|
|
@@ -81,14 +109,13 @@ On first remote start, the CLI:
|
|
|
81
109
|
|
|
82
110
|
A normal `machine-mcp` invocation is a foreground start: it remains attached to the terminal and prints `info` logs. Startup and other state-changing operations use an owner-token/process-identity lock and wait up to 30 seconds for an ordinary concurrent startup to finish instead of failing on a brief launchd/systemd race. After a global package upgrade, the CLI unloads the platform service and also checks the workspace lock for a detached/orphan `--daemon-only` process that the service manager no longer tracks. It terminates only a process whose PID, process start time, entrypoint, canonical workspace, canonical state root, and daemon-only arguments all match, waits up to 15 seconds for the PID and lock to disappear, and then starts the installed version in the foreground. A genuine foreground or unverifiable conflict is left untouched and reported with actionable guidance. To run only in the background, use `machine-mcp service start`; inspect its owner-only logs under `~/.local/state/machine-bridge-mcp/logs/`.
|
|
83
111
|
|
|
84
|
-
Recommended upgrade sequence:
|
|
112
|
+
Recommended upgrade sequence: repeat the package-free temporary-directory installation above, then run:
|
|
85
113
|
|
|
86
114
|
```sh
|
|
87
|
-
npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
|
|
88
115
|
machine-mcp --verbose
|
|
89
116
|
```
|
|
90
117
|
|
|
91
|
-
The global install replaces files on disk but cannot hot-reload an already running Node process; the
|
|
118
|
+
The global install replaces files on disk but cannot hot-reload an already running Node process; the foreground command performs the bounded service and orphan-daemon takeover. Keep `--omit=optional` in the installation step to prevent the development-only optional `fsevents` package from producing a blocked-install-script warning. If takeover fails, run `machine-mcp service status` first; it reports the platform service and workspace daemon separately. Then run `machine-mcp service stop` and retry.
|
|
92
119
|
|
|
93
120
|
Use the printed values in the MCP client:
|
|
94
121
|
|
|
@@ -97,7 +124,7 @@ MCP Server URL: https://<worker>.<account>.workers.dev/mcp
|
|
|
97
124
|
MCP connection password: mcp_password_...
|
|
98
125
|
```
|
|
99
126
|
|
|
100
|
-
The remote authorization flow uses an authorization code, PKCE S256, exact redirect/resource binding, expiring access tokens stored as hashes, and a token-version value for bulk revocation.
|
|
127
|
+
The remote authorization flow uses an authorization code, PKCE S256, exact redirect/resource binding, expiring access tokens stored as hashes, and a token-version value for bulk revocation. After password approval, the Worker constructs the registered callback with the URL API and returns `303 See Other`, so OAuth response parameters are encoded and the browser performs a GET to the callback.
|
|
101
128
|
|
|
102
129
|
## Optional local stdio MCP
|
|
103
130
|
|
|
@@ -128,7 +155,7 @@ Two lower-precedence virtual sources are generated in memory:
|
|
|
128
155
|
- `machine-bridge://defaults/working-agreements` supplies conservative cross-project rules for inspection, scoped changes, preservation of unrelated work, validation, security, Git discipline, and explicit authorization for deployment/publication/destructive operations.
|
|
129
156
|
- `machine-bridge://project-context/current` derives bounded facts from the active repository: target path, common project/build files, package-manager declarations and lockfiles, package script names, runtime constraints, documentation files, and CI entrypoints. It never injects package-script bodies or source contents and does not claim commands were validated.
|
|
130
157
|
|
|
131
|
-
No files are created or modified by this bootstrap. Explicit user and repository instructions load later and therefore override the defaults. `session_bootstrap` supplies the chain during MCP initialization; `agent_context` exposes every source and hash; `resolve_task_capabilities` refreshes project facts, instructions, skills, and
|
|
158
|
+
No files are created or modified by this bootstrap. Explicit user and repository instructions load later and therefore override the defaults. `session_bootstrap` supplies the chain during MCP initialization; `agent_context` exposes every source and hash; `resolve_task_capabilities` refreshes project facts, instructions, skills, commands, and installed-application matches for the current task. `server_info` and `project_overview` expose whether bootstrap and task resolution actually reached the local runtime without retaining raw task text.
|
|
132
159
|
|
|
133
160
|
Optional user-global preferences still live in `~/.config/machine-bridge-mcp/agent.json`:
|
|
134
161
|
|
|
@@ -153,13 +180,13 @@ To disable either automatic layer globally:
|
|
|
153
180
|
|
|
154
181
|
Repositories cannot disable these user-level controls. Editing instruction files or project metadata does not require a daemon restart; start a new MCP conversation or reconnect when initialization-time injection must be guaranteed from the beginning.
|
|
155
182
|
|
|
156
|
-
Skill discovery follows Codex-style progressive disclosure. Default roots
|
|
183
|
+
Skill discovery follows Codex-style progressive disclosure. Default project roots include target-to-project `.agents/skills` and `.codex/skills`; unrestricted policy also enables `~/.agents/skills`, `CODEX_HOME/skills` (normally `~/.codex/skills`), and the Unix admin root. Newly added or edited skills are found on the next resolver/list call without restarting the daemon. Safe root `package.json` script names are also exposed as `package.*` command aliases with bounded deterministic English/Chinese workflow-intent terms; explicit `.machine-bridge/agent.json` commands can override or delete them. Package-script bodies are never injected, but invoking one still runs repository-controlled code with the active local policy. See [Session instructions, defaults, skills, commands, and capability discovery](docs/AGENT_CONTEXT.md).
|
|
157
184
|
|
|
158
185
|
Under canonical `full`, Machine Bridge also exposes structured local automation:
|
|
159
186
|
|
|
160
187
|
- installed application discovery/opening and macOS Accessibility inspection/actions;
|
|
161
188
|
- a packaged Chromium extension that controls the user's existing daily browser profile, active tabs, login state, and windows;
|
|
162
|
-
- current DOM source and frame/open-Shadow-DOM inspection,
|
|
189
|
+
- current DOM source and frame/open-Shadow-DOM inspection, stable semantic element references, actionability waits, fixed trusted mouse/keyboard input, explicit browser waits, tab management, complex multi-field forms, resource-backed secret fields, resource-backed file uploads, and screenshots.
|
|
163
190
|
|
|
164
191
|
One-time browser setup:
|
|
165
192
|
|
|
@@ -168,9 +195,9 @@ machine-mcp browser setup
|
|
|
168
195
|
machine-mcp browser status
|
|
169
196
|
```
|
|
170
197
|
|
|
171
|
-
Load the printed unpacked-extension directory once in Chrome, Edge, Brave, Vivaldi, or another compatible Chromium browser. The extension badge reports connection state, and clicking it opens the saved local pairing page. The local pairing token remains in owner-only state and the loopback pairing page; it is not returned through MCP. For a mass-market release, distribute the same extension as a signed browser-store build rather than asking end users to enable Developer mode. See [Local application and browser automation](docs/LOCAL_AUTOMATION.md).
|
|
198
|
+
Load the printed unpacked-extension directory once in Chrome, Edge, Brave, Vivaldi, or another compatible Chromium browser. After an upgrade that adds permissions, reload the unpacked extension and accept the browser prompt; trusted input uses the Chromium `debugger` permission only for fixed, short-lived DevTools Input commands. The extension badge reports connection state, and clicking it opens the saved local pairing page. The local pairing token remains in owner-only state and the loopback pairing page; it is not returned through MCP. For a mass-market release, distribute the same extension as a signed browser-store build rather than asking end users to enable Developer mode. See [Local application and browser automation](docs/LOCAL_AUTOMATION.md).
|
|
172
199
|
|
|
173
|
-
Machine Bridge can discover, refresh, rank, and load capabilities automatically. The ChatGPT/MCP host still owns tool selection and approval, so the server cannot force a host to expose or invoke a recommended skill, command, app, or browser operation.
|
|
200
|
+
Machine Bridge can discover, refresh, rank, and load capabilities automatically. The ChatGPT/MCP host still owns tool selection and approval, so the server cannot force a host to expose or invoke a recommended skill, command, app, or browser operation. Check `server_info.observability.capability_routing` or `project_overview.capabilityRouting` to distinguish “the host never called the resolver” from “the resolver ran but found no relevant capability.”
|
|
174
201
|
|
|
175
202
|
## Policy controls
|
|
176
203
|
|
|
@@ -319,9 +346,11 @@ The exact `tools/list` response reflects the active local policy. Definitions co
|
|
|
319
346
|
- `browser_status`
|
|
320
347
|
- `pair_browser_extension`
|
|
321
348
|
- `browser_list_tabs`
|
|
349
|
+
- `browser_manage_tabs` — create, activate, or close tabs
|
|
322
350
|
- `browser_get_source` — bounded current DOM HTML, including selected frames
|
|
323
|
-
- `browser_inspect_page`
|
|
324
|
-
- `
|
|
351
|
+
- `browser_inspect_page` — semantic controls, stable per-document refs, state, and geometry
|
|
352
|
+
- `browser_wait` — bounded URL/load/text/element-state waits
|
|
353
|
+
- `browser_action` — actionability checks plus `auto`, `trusted`, or `dom` input mode
|
|
325
354
|
- `browser_fill_form`
|
|
326
355
|
- `browser_upload_files` — registered local resources to file inputs
|
|
327
356
|
- `browser_screenshot`
|
|
@@ -357,7 +386,7 @@ Managed jobs are non-interactive and persist independently of the MCP connection
|
|
|
357
386
|
|
|
358
387
|
### Processes
|
|
359
388
|
|
|
360
|
-
- `run_local_command` —
|
|
389
|
+
- `run_local_command` — fixed argv execution of an explicit or automatic package-script command
|
|
361
390
|
- `run_process` — one-shot argv execution without a shell
|
|
362
391
|
- `start_process`
|
|
363
392
|
- `read_process`
|
|
@@ -424,6 +453,8 @@ Default state roots:
|
|
|
424
453
|
|
|
425
454
|
State/config writes use owner-only temporary files, `fsync`, and atomic replacement. Exclusive locks are fully written before a same-directory hard-link claim becomes visible; lock ownership includes a token and process start time so PID reuse and lock replacement cannot silently transfer ownership. Only successfully read but invalid JSON is retained as a bounded corrupt backup; permission, symbolic-link, size, encoding, and I/O failures remain explicit. A custom state root must not equal, contain, or be contained by the selected workspace. Resource source paths are redacted from `status` output. Active managed-job plans are owner-only and are deleted after a terminal result; bounded redacted results are retained temporarily. Uninstall first acquires a state-root maintenance lock that blocks new profile/state operations and state-backed operations from already constructed managed-job/browser managers, stops the platform service and all verified workspace daemons before removing definitions, rechecks jobs and locks, then validates markers, canonical paths, workspace/source exclusions, and known contents before recursive deletion.
|
|
426
455
|
|
|
456
|
+
The relay honors standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment routing. Invalid or unsupported proxy configuration fails fast; runtime status reports only `direct`, `proxy`, or `invalid-proxy-configuration`, never proxy URLs or credentials.
|
|
457
|
+
|
|
427
458
|
Default foreground logs report authenticated relay readiness, readable persistent-degradation summaries, and recovery rather than raw WebSocket callbacks or JSON field dumps. Brief self-healing disconnects and close codes/reasons are debug-only. Stalled connection attempts have a deadline, and sustained-outage reminders use autonomous exponential backoff. Every per-tool event—including success, failure, cancellation, and slow-call timing—also appears only at `--log-level debug` or `--verbose`. Background services use `warn`, so ordinary tool outcomes and brief network changes do not fill daemon logs. Log messages and structured fields are bounded, secret-like keys and known token formats are redacted, and tool arguments/results are not written. See [docs/LOGGING.md](docs/LOGGING.md) and [docs/OPERATIONS.md](docs/OPERATIONS.md).
|
|
428
459
|
|
|
429
460
|
## Development and verification
|
package/SECURITY.md
CHANGED
|
@@ -57,6 +57,10 @@ Machine Bridge prepends a package-controlled built-in working-agreement block an
|
|
|
57
57
|
|
|
58
58
|
Repository and user instruction files remain untrusted content from the model's perspective. `agent_context` returns their text in deterministic precedence order but does not certify correctness or safety. A malicious `AGENTS.md`, custom instruction file, filename, script name, or `SKILL.md` can attempt prompt injection or recommend destructive operations. Use only trusted repositories and skill roots, keep host approvals/policy enabled, or isolate the bridge externally.
|
|
59
59
|
|
|
60
|
+
Automatic `package.*` commands expose only validated script names and fixed package-manager argv; they do not expose script bodies. Executing one still runs repository-controlled package-script code with local-user authority and is not a sandbox or trust upgrade.
|
|
61
|
+
|
|
62
|
+
Relay proxy selection honors `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. Only HTTP(S) proxy URLs are accepted. Proxy endpoints, credentials, and authorization headers are not returned through MCP or written to operational logs; status exposes only coarse route state.
|
|
63
|
+
|
|
60
64
|
Skill loading is non-executing. `load_local_skill` returns an entrypoint and bounded file inventory; scripts remain inert until a separate process or command tool is called. Symlinked skill directories are followed only after canonical path-policy validation, while symbolic-link `SKILL.md` entrypoints are rejected. Traversal, cycles, content, summaries, and inventory are bounded.
|
|
61
65
|
|
|
62
66
|
Registered commands are available only under direct-execution-capable policy. They use argv spawning rather than shell parsing, reject undeclared caller arguments, and enforce the manifest timeout as a ceiling. They are not an approval or sandbox boundary: a repository-controlled package script, interpreter, compiler, or executable can still run arbitrary code with local-user authority. Deeper manifests can override or remove inherited commands, so callers must obtain context for the actual target path rather than assuming the repository-root registry applies everywhere.
|
|
@@ -67,11 +71,11 @@ The global `model_instructions_file` is an explicit user trust decision. It is r
|
|
|
67
71
|
|
|
68
72
|
## Browser and application automation
|
|
69
73
|
|
|
70
|
-
Canonical `full` exposes structured browser and desktop UI authority. The browser extension operates the user's existing Chromium profile, including active login state, and has broad host access so it can inspect arbitrary pages
|
|
74
|
+
Canonical `full` exposes structured browser and desktop UI authority. The browser extension operates the user's existing Chromium profile, including active login state, and has broad host access plus Chromium `debugger` permission so it can inspect arbitrary pages, fill complex forms, and issue trusted input. An authorized client may therefore read page content, click controls, submit forms, upload registered files, and cause transactions with the user's browser identity. macOS Accessibility actions similarly operate applications with the local user's UI authority.
|
|
71
75
|
|
|
72
76
|
The browser loopback broker validates loopback `Host`, requires a random owner-only bearer subprotocol, accepts extension sockets only with a `chrome-extension://` origin, and authenticates additional local runtimes separately. Initial extension pairing is trust-on-first-use; after pairing, a different localhost page cannot replace the stored broker unless the user clicks the extension action while the genuine pairing page is active. The pairing token is never returned by MCP or written to operational logs. These controls defend against casual cross-origin and DNS-rebinding access; they do not protect against malicious code already running as the same OS user or a compromised browser extension profile.
|
|
73
77
|
|
|
74
|
-
Caller-supplied JavaScript, AppleScript,
|
|
78
|
+
Caller-supplied JavaScript, AppleScript, JXA source, and arbitrary DevTools methods are deliberately unsupported. Actions use fixed implementation code and structured selectors. The debugger adapter is restricted to fixed `Input` commands, attaches only around one trusted action, and detaches in `finally`; `input_mode` makes fallback behavior explicit. The broker validates a versioned extension capability hello before activating a connection and does not let an invalid replacement candidate displace an already compatible extension. This removes an avoidable arbitrary-evaluation surface but does not make DOM or Accessibility actions safe. Pages can contain prompt injection, misleading labels, hidden consequences, cross-origin frames unavailable to inspection, or state that changes between inspection and submission. High-impact account, financial, legal, medical, publishing, deletion, and purchase actions require contextual review and any host/user confirmation the client provides.
|
|
75
79
|
|
|
76
80
|
Text and file resources can be injected locally so their contents do not appear in MCP arguments or results. The destination page/application still receives the value, and remote tool metadata/results still traverse the Worker and host. Page source and screenshots can themselves contain secrets. Browser/app tool arguments, source, screenshots, field values, and results are intentionally omitted from operational logs.
|
|
77
81
|
|
|
@@ -141,7 +145,7 @@ Runner diagnostic logs are owner-only and do not receive child stdout/stderr. St
|
|
|
141
145
|
|
|
142
146
|
## OAuth and public endpoints
|
|
143
147
|
|
|
144
|
-
Remote mode uses authorization code flow with PKCE S256, exact redirect/resource/client binding, expiring authorization codes and access tokens, hashed token storage, token-version revocation, and bounded dynamic client registration.
|
|
148
|
+
Remote mode uses authorization code flow with PKCE S256, exact redirect/resource/client binding, expiring authorization codes and access tokens, hashed token storage, token-version revocation, and bounded dynamic client registration. Successful consent constructs the registered callback through the URL API and returns `303 See Other`; response parameters are encoded rather than concatenated into an unchecked header string.
|
|
145
149
|
|
|
146
150
|
The authorization page displays the validated client name and redirect URI. Enter the connection password only after initiating the connection and recognizing both values.
|
|
147
151
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
if (globalThis.__machineBridgeDevtoolsInput) return;
|
|
3
|
+
|
|
4
|
+
const MODIFIERS = Object.freeze({ Alt: 1, Control: 2, Meta: 4, Shift: 8 });
|
|
5
|
+
const tabQueues = new Map();
|
|
6
|
+
const KEY_DATA = Object.freeze({
|
|
7
|
+
Enter: { code: "Enter", virtualKeyCode: 13 },
|
|
8
|
+
Tab: { code: "Tab", virtualKeyCode: 9 },
|
|
9
|
+
Escape: { code: "Escape", virtualKeyCode: 27 },
|
|
10
|
+
Backspace: { code: "Backspace", virtualKeyCode: 8 },
|
|
11
|
+
Delete: { code: "Delete", virtualKeyCode: 46 },
|
|
12
|
+
ArrowLeft: { code: "ArrowLeft", virtualKeyCode: 37 },
|
|
13
|
+
ArrowUp: { code: "ArrowUp", virtualKeyCode: 38 },
|
|
14
|
+
ArrowRight: { code: "ArrowRight", virtualKeyCode: 39 },
|
|
15
|
+
ArrowDown: { code: "ArrowDown", virtualKeyCode: 40 },
|
|
16
|
+
Home: { code: "Home", virtualKeyCode: 36 },
|
|
17
|
+
End: { code: "End", virtualKeyCode: 35 },
|
|
18
|
+
PageUp: { code: "PageUp", virtualKeyCode: 33 },
|
|
19
|
+
PageDown: { code: "PageDown", virtualKeyCode: 34 },
|
|
20
|
+
Space: { code: "Space", virtualKeyCode: 32, key: " " },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
async function perform(tabId, action, details = {}) {
|
|
24
|
+
if (!Number.isInteger(tabId) || tabId < 1) throw new Error("trusted input requires a valid tab");
|
|
25
|
+
const previous = tabQueues.get(tabId) || Promise.resolve();
|
|
26
|
+
const current = previous.catch(() => {}).then(() => withDebugger(tabId, async (send) => {
|
|
27
|
+
if (action === "click") return mouseClick(send, details.point, 1);
|
|
28
|
+
if (action === "double_click") return mouseClick(send, details.point, 2);
|
|
29
|
+
if (action === "hover") return mouseMove(send, details.point);
|
|
30
|
+
if (action === "press") return pressKey(send, details.key || "Enter");
|
|
31
|
+
if (action === "type_text") return send("Input.insertText", { text: String(details.text ?? "") });
|
|
32
|
+
throw new Error(`trusted input does not support '${action}'`);
|
|
33
|
+
}));
|
|
34
|
+
tabQueues.set(tabId, current);
|
|
35
|
+
try { return await current; }
|
|
36
|
+
finally { if (tabQueues.get(tabId) === current) tabQueues.delete(tabId); }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function withDebugger(tabId, operation) {
|
|
40
|
+
const target = { tabId };
|
|
41
|
+
let attached = false;
|
|
42
|
+
try {
|
|
43
|
+
await chrome.debugger.attach(target, "1.3");
|
|
44
|
+
attached = true;
|
|
45
|
+
const send = (method, params = {}) => chrome.debugger.sendCommand(target, method, params);
|
|
46
|
+
return await operation(send);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(`trusted browser input unavailable: ${cleanError(error)}`);
|
|
49
|
+
} finally {
|
|
50
|
+
if (attached) {
|
|
51
|
+
try { await chrome.debugger.detach(target); } catch {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function mouseMove(send, point) {
|
|
57
|
+
const { x, y } = normalizePoint(point);
|
|
58
|
+
await send("Input.dispatchMouseEvent", { type: "mouseMoved", x, y, button: "none" });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function mouseClick(send, point, clickCount) {
|
|
62
|
+
const { x, y } = normalizePoint(point);
|
|
63
|
+
await mouseMove(send, { x, y });
|
|
64
|
+
for (let count = 1; count <= clickCount; count += 1) {
|
|
65
|
+
await send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", buttons: 1, clickCount: count });
|
|
66
|
+
await send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", buttons: 0, clickCount: count });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function pressKey(send, shortcut) {
|
|
71
|
+
const parsed = parseShortcut(shortcut);
|
|
72
|
+
const down = {
|
|
73
|
+
type: parsed.text ? "keyDown" : "rawKeyDown",
|
|
74
|
+
key: parsed.key,
|
|
75
|
+
code: parsed.code,
|
|
76
|
+
modifiers: parsed.modifiers,
|
|
77
|
+
windowsVirtualKeyCode: parsed.virtualKeyCode,
|
|
78
|
+
nativeVirtualKeyCode: parsed.virtualKeyCode,
|
|
79
|
+
...(parsed.text ? { text: parsed.text, unmodifiedText: parsed.text } : {}),
|
|
80
|
+
};
|
|
81
|
+
await send("Input.dispatchKeyEvent", down);
|
|
82
|
+
await send("Input.dispatchKeyEvent", {
|
|
83
|
+
type: "keyUp",
|
|
84
|
+
key: parsed.key,
|
|
85
|
+
code: parsed.code,
|
|
86
|
+
modifiers: parsed.modifiers,
|
|
87
|
+
windowsVirtualKeyCode: parsed.virtualKeyCode,
|
|
88
|
+
nativeVirtualKeyCode: parsed.virtualKeyCode,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseShortcut(value) {
|
|
93
|
+
const parts = String(value || "Enter").split("+").map((part) => part.trim()).filter(Boolean);
|
|
94
|
+
const keyName = parts.pop() || "Enter";
|
|
95
|
+
let modifiers = 0;
|
|
96
|
+
for (const raw of parts) {
|
|
97
|
+
const name = raw === "Ctrl" ? "Control" : raw === "Cmd" || raw === "Command" ? "Meta" : raw;
|
|
98
|
+
if (!(name in MODIFIERS)) throw new Error(`unsupported key modifier: ${raw}`);
|
|
99
|
+
modifiers |= MODIFIERS[name];
|
|
100
|
+
}
|
|
101
|
+
const known = KEY_DATA[keyName];
|
|
102
|
+
if (known) return {
|
|
103
|
+
key: known.key || keyName,
|
|
104
|
+
code: known.code,
|
|
105
|
+
virtualKeyCode: known.virtualKeyCode,
|
|
106
|
+
modifiers,
|
|
107
|
+
text: "",
|
|
108
|
+
};
|
|
109
|
+
if ([...keyName].length !== 1) throw new Error(`unsupported key: ${keyName}`);
|
|
110
|
+
const character = keyName;
|
|
111
|
+
const upper = character.toUpperCase();
|
|
112
|
+
return {
|
|
113
|
+
key: character,
|
|
114
|
+
code: /[A-Za-z]/.test(character) ? `Key${upper}` : "",
|
|
115
|
+
virtualKeyCode: upper.codePointAt(0),
|
|
116
|
+
modifiers,
|
|
117
|
+
text: modifiers & (MODIFIERS.Control | MODIFIERS.Meta | MODIFIERS.Alt) ? "" : character,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizePoint(point) {
|
|
122
|
+
const x = Number(point?.x);
|
|
123
|
+
const y = Number(point?.y);
|
|
124
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) throw new Error("trusted input target has no usable viewport point");
|
|
125
|
+
return { x, y };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function cleanError(error) {
|
|
129
|
+
return String(error?.message || error || "unknown error").replace(/\s+/g, " ").slice(0, 500);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Object.defineProperty(globalThis, "__machineBridgeDevtoolsInput", {
|
|
133
|
+
value: Object.freeze({ perform }),
|
|
134
|
+
configurable: false,
|
|
135
|
+
});
|
|
136
|
+
})();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Machine Bridge Browser",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"tabs",
|
|
8
|
+
"debugger",
|
|
8
9
|
"scripting",
|
|
9
10
|
"storage",
|
|
10
11
|
"alarms"
|
|
@@ -29,5 +30,5 @@
|
|
|
29
30
|
"action": {
|
|
30
31
|
"default_title": "Machine Bridge Browser"
|
|
31
32
|
},
|
|
32
|
-
"version_name": "0.
|
|
33
|
+
"version_name": "0.14.0"
|
|
33
34
|
}
|