machine-bridge-mcp 0.3.3 → 0.4.2

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/SECURITY.md CHANGED
@@ -2,83 +2,135 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
- Security fixes are applied to the latest released version. Upgrade before reporting an issue that is already fixed on `main`.
5
+ Security fixes are applied to the latest released version. Upgrade before reporting an issue already fixed on `main`.
6
6
 
7
7
  ## Reporting a vulnerability
8
8
 
9
- Do not open a public issue for an undisclosed vulnerability. Use GitHub's private vulnerability reporting feature for this repository. Include:
9
+ Do not open a public issue for an undisclosed vulnerability. Use GitHub private vulnerability reporting for this repository. Include:
10
10
 
11
11
  - affected version and operating system;
12
- - deployment mode and relevant policy flags;
13
- - a minimal reproduction;
12
+ - remote or stdio transport;
13
+ - selected profile and relevant flags;
14
+ - minimal reproduction;
14
15
  - expected and observed impact;
15
- - whether credentials, filesystem data, or command execution were exposed.
16
+ - whether credentials, filesystem data, process authority, or network access were exposed.
16
17
 
17
- Do not include live MCP passwords, daemon secrets, OAuth tokens, Cloudflare credentials, private keys, or unrelated local files. Rotate any credential used in a reproduction.
18
+ Do not include live MCP passwords, daemon secrets, OAuth tokens, Cloudflare credentials, private keys, or unrelated local files. Rotate credentials used in a reproduction.
18
19
 
19
- ## Trust model
20
+ ## Core trust model
20
21
 
21
- The following components are trusted:
22
+ Trusted components are:
22
23
 
23
- 1. The local user account running the daemon.
24
- 2. The installed package/source checkout.
25
- 3. The user's Cloudflare account and deployed Worker.
26
- 4. An MCP client that has completed OAuth authorization with the connection password.
24
+ 1. the local OS user running the runtime;
25
+ 2. the installed package/source checkout and its dependencies;
26
+ 3. for remote mode, the user's Cloudflare account and deployed Worker;
27
+ 4. an MCP client explicitly authorized through OAuth or launched locally through trusted stdio configuration.
27
28
 
28
- The Worker is a relay and authorization boundary. The local daemon is the filesystem and command-execution boundary. A successfully authorized MCP client can invoke every tool enabled by the daemon policy and can receive the resulting file contents and command output.
29
+ An authorized client can invoke every tool exposed by the selected profile and receive its results.
29
30
 
30
- This project does not provide a sandbox for shell commands. `exec_command` has the operating-system permissions of the local user. Workspace path confinement limits filesystem tools, but it cannot constrain commands executed by the shell. Use `--no-exec` when shell access is unnecessary.
31
+ The Worker is a remote authentication and relay boundary. The local runtime is the filesystem and process boundary. Stdio bypasses the Worker and relies on local process/configuration trust.
31
32
 
32
- ## Primary risks and controls
33
+ ## Profiles are capability sets, not sandboxes
33
34
 
34
- ### Credential disclosure
35
+ The default for newly selected workspaces is `full`, which prioritizes ease of use over least privilege.
35
36
 
36
- Local state contains the MCP password and daemon secret. State, lock, temporary secret, and service-log files use owner-only permissions where the platform supports them. State writes are atomic. Logs recursively and fully redact common credential fields and known token formats.
37
+ - `full` exposes all tools, unrestricted direct filesystem paths, absolute path output, and the complete parent process environment.
38
+ - `agent` exposes file mutation, direct argv execution, and process sessions while keeping direct filesystem tools workspace-confined and the process environment isolated.
39
+ - `edit` exposes read and mutation tools without process execution.
40
+ - `review` exposes read-only workspace/Git/image tools.
37
41
 
38
- Terminal output can intentionally display the MCP password. Avoid recording first-run output in shared shell history, CI logs, screen recordings, or support tickets. Use `--no-print-credentials` for recorded sessions.
42
+ `run_process` avoids shell parsing, which removes one injection class, but an executable such as `node`, `python`, a package manager, compiler, test runner, or repository script can execute arbitrary code. Direct mode therefore has effectively broad local-user authority once an attacker controls argv or executed code.
39
43
 
40
- ### Filesystem exposure
44
+ `exec_command` has both executable authority and shell expansion. Use `--no-exec` or `review`/`edit` when process execution is unnecessary.
41
45
 
42
- Filesystem tools are confined to the canonical selected workspace by default, including symbolic-link resolution. `--unrestricted-paths` exposes absolute and parent paths and should be used only for a deliberate, short-lived session.
46
+ For untrusted repositories or instructions, run the bridge inside a disposable VM/container or under a dedicated low-privilege OS account. On macOS and Windows, this external isolation is especially important. The project does not claim an in-process OS sandbox.
43
47
 
44
- Sensitive filenames are not automatically blocked inside the workspace. Select the narrowest practical workspace and use `--no-write` for review-only sessions.
48
+ ## Filesystem exposure
45
49
 
46
- ### Command execution
50
+ Direct filesystem scope is profile-dependent. The default `full` profile is unrestricted. The `agent`, `edit`, and `review` profiles confine direct filesystem tools to the canonical selected workspace, including symbolic-link resolution.
47
51
 
48
- `exec_command` is enabled by default but can be disabled with `--no-exec`. Commands run with a minimal environment unless `--full-env` is supplied. Minimal environment handling reduces accidental environment-secret disclosure; it does not sandbox filesystem, network, process, or credential-store access available to the user account.
52
+ The default `full` profile returns absolute paths. Narrower profiles return workspace-relative paths to reduce username and directory-layout disclosure. Path display and access scope remain separate controls.
49
53
 
50
- ### OAuth and public endpoints
54
+ Sensitive-looking names are not blocked inside the workspace. Files such as `.env`, private keys, credentials, database dumps, and production configuration remain readable when they are in the selected workspace. Choose the narrowest practical workspace.
51
55
 
52
- OAuth uses authorization codes, PKCE S256, exact redirect/resource binding, hashed access-token storage, expiring codes/tokens, and a version value that revokes existing tokens after rotation. Client registration, codes, tokens, pending calls, body sizes, and failed password attempts are bounded.
56
+ Processes are not confined by the filesystem-tool resolver. They can access paths, networks, processes, credential stores, and devices available to the local user.
53
57
 
54
- The authorization page displays the validated client name and redirect URI. Do not enter the connection password unless you initiated the connection and recognize both values. Browser-originated requests are same-origin by default; exact additional origins require `MBM_ALLOWED_ORIGINS` and receive explicit CORS headers. Loopback redirect URIs do not grant browser-origin access.
58
+ ## Mutation integrity
55
59
 
56
- Public health and metadata endpoints do not expose live daemon policy or connection status. The daemon handshake does not upload the local workspace name, path hash, or process ID. Authenticated `server_info` exposes only bounded operational metadata needed to diagnose the relay.
60
+ Writes are bounded, reject symbolic-link/non-regular destinations, use same-directory staging, and commit atomically per file. Create-only commit fails if a concurrent destination appears. Expected hashes and exact edits reduce accidental stale overwrites.
57
61
 
58
- ### Relay and denial of service
62
+ Patch operations prevalidate all paths/content, reject canonical collisions, recheck source hashes and destination absence, serialize bridge mutations, maintain backups, and roll back on ordinary commit errors.
59
63
 
60
- Only one daemon socket is active. New connections replace old ones. Pending calls are socket-bound and concurrency-limited. Request and output sizes are bounded. Dynamic registration is limited globally and per keyed-HMAC source identity; the source IP is not stored directly and inactive clients expire.
64
+ These controls do not defend against a malicious process running under the same user racing filesystem metadata, nor do they make a multi-directory patch power-loss atomic.
61
65
 
62
- These controls reduce accidental exhaustion and simple abuse. The CLI uses the package-installed Wrangler binary and does not fall back to downloading an unpinned command through `npx`. They do not replace Cloudflare account protections, rate-limiting/WAF rules, or cost alerts for an internet-facing deployment.
66
+ ## Credential exposure
67
+
68
+ Local state contains the MCP connection password and daemon secret. State, lock, temporary secret, runtime, and service-log files use owner-only permissions where supported. State writes are atomic. Logs recursively redact known credential fields and token formats and neutralize control characters.
69
+
70
+ First-run or explicit credential output can intentionally display the MCP password. Avoid shared terminal logs, shell recordings, screenshots, CI output, or support tickets. Use `--no-print-credentials` for recorded sessions.
71
+
72
+ The default `full` profile passes the complete parent environment. Narrower profiles replace HOME, temp, and common cache paths and do not pass arbitrary parent variables. The isolated mode reduces accidental environment-secret leakage; it does not prevent code from explicitly accessing known resources.
73
+
74
+ ## OAuth and public endpoints
75
+
76
+ 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.
77
+
78
+ The authorization page displays the validated client name and redirect URI. Enter the connection password only after initiating the connection and recognizing both values.
79
+
80
+ Password failures and registrations are limited by deployment-keyed HMAC source identity. Browser requests are same-origin unless an exact origin is listed in `MBM_ALLOWED_ORIGINS`; loopback OAuth redirect permission does not grant browser-origin access.
81
+
82
+ Public health and metadata do not expose live workspace or daemon status. The daemon handshake omits workspace path/name/hash and process ID.
83
+
84
+ ## Relay and denial of service
85
+
86
+ Only one authenticated daemon is active. Candidates have a handshake deadline and cannot displace the current daemon before success. Pending calls are socket-bound, client-request-bound, concurrency-limited, size-limited, and timed out. Duplicate in-flight request IDs for one access token are rejected.
87
+
88
+ Request bodies, WebSocket messages, tool outputs, traversals, sessions, stdin writes, OAuth records, clients, codes, tokens, and failure identities are bounded. Disconnect/replacement terminates active child process trees.
89
+
90
+ These controls reduce accidental exhaustion and simple abuse. They do not replace Cloudflare account MFA, WAF/rate limits, usage alerts, or cost controls for an internet-facing Worker.
91
+
92
+ ## Process sessions
93
+
94
+ Process-session IDs are random and valid only inside one runtime. Output buffers and session counts are bounded. Sessions are killed on runtime stop or remote connection loss/replacement and expire from retained state after exit.
95
+
96
+ Sessions use pipes, not a PTY. Do not assume terminal-oriented programs will behave safely or correctly. Process output may contain secrets; it is returned to the authorized client but is not intentionally written to operational logs.
97
+
98
+ ## Images and rich content
99
+
100
+ `view_image` accepts only signature-validated PNG, JPEG, GIF, and WebP under the size cap. SVG is intentionally excluded because it is active document content rather than a simple raster image. Image bytes pass through the Worker in remote mode and are visible to the authorized MCP client.
101
+
102
+ ## Logs and privacy
103
+
104
+ Operational logs record coarse metadata and error classes, not tool arguments, command text, stdin, file/patch contents, or outputs. Unexpected Worker exceptions are reduced to error classes. Git author email is omitted from `git_log` unless explicitly requested.
105
+
106
+ No logging policy can prevent data from being returned to a client that explicitly invokes an enabled tool. The Worker necessarily relays remote tool arguments and results; this is not end-to-end encryption against the user's Cloudflare execution environment.
63
107
 
64
108
  ## Hardening checklist
65
109
 
66
- For a high-sensitivity workspace:
110
+ For sensitive review:
111
+
112
+ ```sh
113
+ machine-mcp --workspace /narrow/project --profile review --no-print-credentials
114
+ ```
115
+
116
+ For controlled editing without execution:
67
117
 
68
- ```zsh
69
- machine-mcp --workspace /narrow/project --no-write --no-exec --no-print-credentials
118
+ ```sh
119
+ machine-mcp --workspace /narrow/project --profile edit --no-print-credentials
70
120
  ```
71
121
 
72
122
  Also:
73
123
 
74
- - keep the local OS and Node.js supported and patched;
75
- - enable MFA on the Cloudflare and GitHub accounts;
76
- - do not set `MBM_ALLOWED_ORIGINS` broadly;
77
- - avoid `--full-env` and `--unrestricted-paths`;
124
+ - patch the OS and use supported Node.js releases;
125
+ - enable MFA on Cloudflare, GitHub, and npm accounts;
126
+ - do not configure broad CORS origins;
127
+ - avoid `--full-env`, `--unrestricted-paths`, and `--absolute-paths` unless required;
128
+ - inspect client names and OAuth redirect URIs;
78
129
  - rotate secrets after suspected disclosure;
79
- - inspect `machine-mcp status` and `machine-mcp service status`;
80
- - uninstall the Worker and local state when the bridge is no longer needed.
130
+ - inspect `status`, `doctor`, and service status;
131
+ - remove the Worker and state when remote access is no longer needed;
132
+ - use external OS isolation for untrusted code.
81
133
 
82
134
  ## Out of scope
83
135
 
84
- The project cannot prevent an authorized MCP client from requesting sensitive data that the enabled tools can access. It also cannot make arbitrary shell commands safe. Model-level prompt injection and unsafe operator approval remain outside the transport's security guarantees; use narrow workspaces and restrictive policy flags as the primary controls.
136
+ The project cannot prevent an authorized client from requesting data accessible to enabled tools, make arbitrary local executables safe, identify all sensitive content, or neutralize model prompt injection. Operator approval and narrow capability selection remain primary controls.
@@ -1,102 +1,169 @@
1
1
  # Architecture
2
2
 
3
+ ## Design goal
4
+
5
+ The system separates three questions that are often conflated:
6
+
7
+ 1. **Who may request a tool?** Remote OAuth or local process ownership.
8
+ 2. **Which tools exist?** The selected local policy profile.
9
+ 3. **What authority does a tool have?** Canonical workspace boundaries for direct filesystem tools and local-user authority for processes.
10
+
11
+ No transport is treated as a sandbox. Both transports invoke the same local runtime.
12
+
3
13
  ## Components
4
14
 
5
- ### CLI
15
+ ### Shared tool catalog
6
16
 
7
- The CLI selects a canonical workspace, manages per-workspace state and secrets, serializes startup/deploy/rotation work with a startup lock, deploys the Worker, installs an optional login service, acquires the daemon lock, and starts the daemon. Worker deployment is content-hashed; a hash is persisted only after the expected Worker version passes health checks.
17
+ `src/shared/tool-catalog.json` is the single source of truth for tool names, descriptions, input schemas, annotations, and policy availability. The Worker and stdio runtime import it directly. A catalog test rejects duplicate names, unknown availability classes, open schemas, missing annotations, profile drift, and a second hand-maintained Worker catalog.
8
18
 
9
- ### Cloudflare Worker and Durable Object
19
+ ### CLI and state layer
20
+
21
+ The CLI canonicalizes workspaces, resolves policy profiles, maintains per-workspace state and credentials, serializes startup/deploy/rotation with locks, deploys the Worker, installs optional platform-native autostart, and starts either remote daemon or stdio mode.
10
22
 
11
- All requests for one deployed Worker route to a named Durable Object. OAuth metadata mutations are serialized through an explicit in-object critical section so concurrent registration, authorization, token exchange, and token verification cannot overwrite one another. The object owns:
23
+ A canonical workspace receives an independent profile, Worker name, secret set, daemon/startup locks, and state file. State schema version 3 removes obsolete local API state and records the expanded policy model.
12
24
 
13
- - OAuth client, authorization-code, access-token, and throttling metadata;
14
- - the active daemon WebSocket and a minimal attachment containing connection time, enabled tools, and policy booleans;
15
- - the bounded in-memory map of pending daemon calls.
25
+ ### Local runtime
16
26
 
17
- The daemon attachment deliberately omits the local workspace name, path hash, and process ID.
27
+ `LocalDaemon` is transport-independent despite its historical name. It owns:
18
28
 
19
- The Worker authenticates MCP HTTP requests and converts `tools/call` requests into daemon WebSocket messages. It does not have local filesystem or process access.
29
+ - canonical path resolution and display-path privacy;
30
+ - file, text search, image, patch, and Git operations;
31
+ - direct and shell process execution;
32
+ - process-session buffers and stdin lifecycle;
33
+ - mutation serialization;
34
+ - child-process tracking and cancellation;
35
+ - output, traversal, concurrency, and time limits.
20
36
 
21
- ### Local daemon
37
+ Remote mode attaches WebSocket connection/reconnect behavior. Stdio mode invokes the same runtime directly.
22
38
 
23
- The daemon validates that the Worker endpoint is a credential-free HTTPS origin, then creates an outbound authenticated WebSocket. It advertises only tools enabled by policy, validates every incoming call envelope, resolves filesystem paths, executes local operations, and returns bounded results.
39
+ ### Stdio MCP server
24
40
 
25
- ### Autostart provider
41
+ The stdio server implements newline-delimited JSON-RPC over stdin/stdout. It negotiates supported MCP versions, advertises policy-filtered tools, returns text plus structured content, supports native image content, maps cancellation notifications to runtime call IDs, and sends logs only to stderr.
42
+
43
+ ### Cloudflare Worker and Durable Object
26
44
 
27
- The service layer emits platform-native definitions for launchd, systemd user services, or Windows Scheduled Tasks. It stores no credential in the service definition; the daemon loads credentials from owner-only local state.
45
+ All requests for a deployed Worker route to one named Durable Object. It owns:
46
+
47
+ - OAuth clients, authorization codes, hashed access-token records, and throttling metadata;
48
+ - one active authenticated daemon WebSocket plus bounded candidate sockets;
49
+ - policy/tool metadata attached to the active socket;
50
+ - a bounded in-memory map of pending daemon calls.
51
+
52
+ The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates cancellation by access-token hash and JSON-RPC ID, and formats text/structured/image results. It has no local filesystem or process API.
53
+
54
+ The daemon attachment deliberately omits workspace path/name/hash and process ID. Explicit authenticated tools may return workspace metadata according to local path-display policy.
55
+
56
+ ### Autostart layer
57
+
58
+ The service layer emits launchd, systemd-user, or Windows Scheduled Task definitions. Credentials are not embedded in service definitions; the daemon loads owner-only state. The exact policy is stored in owner-only state; platform service definitions contain only the workspace and state-root selectors.
28
59
 
29
60
  ## Trust boundaries
30
61
 
31
62
  ```mermaid
32
63
  flowchart LR
33
- C[Remote MCP client] -->|HTTPS + OAuth bearer token| W[Worker]
34
- W -->|Durable Object storage| O[OAuth metadata]
35
- W -->|authenticated WebSocket tool call| D[Local daemon]
36
- D -->|workspace-scoped file operations| F[Selected workspace]
37
- D -->|optional shell execution| P[User processes / OS]
38
- L[CLI] --> W
39
- L --> D
40
- L --> S[Autostart provider]
64
+ C[Remote MCP client] -->|HTTPS + OAuth bearer token| W[Worker / Durable Object]
65
+ W -->|authenticated bounded WebSocket calls| R[Local runtime]
66
+ L[Local MCP client] -->|stdio JSON-RPC| R
67
+ R -->|canonical workspace tools| F[Selected workspace]
68
+ R -->|optional direct/shell processes| P[Local user / OS / network]
69
+ CLI[CLI + state] --> W
70
+ CLI --> R
71
+ CLI --> S[Autostart provider]
41
72
  ```
42
73
 
43
- The OAuth boundary determines which remote client may call tools. The daemon policy determines which tools exist. The workspace path resolver determines the default filesystem boundary. Shell execution is intentionally outside the filesystem-tool sandbox and inherits the local user's authority.
74
+ Remote OAuth determines which client may call tools. Local stdio access relies on the local process and configuration boundary. Policy determines which tools are advertised. Canonical resolution limits direct filesystem tools. Processes retain local-user authority and can escape workspace constraints through their own code or system calls.
44
75
 
45
- ## Request lifecycle
76
+ ## Remote request lifecycle
46
77
 
47
- 1. The MCP client discovers OAuth and protected-resource metadata.
78
+ 1. The MCP client discovers protected-resource and authorization-server metadata.
48
79
  2. It dynamically registers bounded redirect metadata.
49
- 3. The authorization request is validated before a password form is shown.
50
- 4. The user verifies the client and redirect URI and supplies the connection password.
51
- 5. A five-minute authorization code bound to client, redirect, resource, scope, and PKCE challenge is created.
52
- 6. A valid verifier exchanges the code for a hashed, expiring access-token record.
53
- 7. An authenticated `tools/call` is assigned a random call ID and bound to the current daemon socket.
54
- 8. The daemon validates the envelope, executes the enabled tool, and returns a bounded result.
55
- 9. The Durable Object accepts the result only from the same socket that received the call.
80
+ 3. The Worker validates authorization parameters before displaying a password form.
81
+ 4. The user verifies client name and redirect URI and enters the connection password.
82
+ 5. The Worker creates a five-minute code bound to client, redirect, resource, scope, and PKCE challenge.
83
+ 6. A valid verifier exchanges the one-time code for an expiring bearer token; only its hash is stored.
84
+ 7. The MCP client initializes and negotiates a supported protocol version.
85
+ 8. `tools/list` is derived from the active daemon handshake; without a daemon, only `server_info` is advertised.
86
+ 9. `tools/call` receives a random relay call ID and is bound to the current socket and authenticated client request key.
87
+ 10. The runtime validates policy and arguments, executes the tool, and returns a bounded result.
88
+ 11. The Durable Object accepts a result only from the socket that received that call.
89
+ 12. A matching cancellation notification removes the pending call, tells the daemon to cancel, and terminates any child processes bound to it.
90
+
91
+ Duplicate in-flight JSON-RPC IDs for the same access token are rejected so cancellation cannot ambiguously target multiple calls.
92
+
93
+ ## Stdio request lifecycle
94
+
95
+ 1. The local client launches `machine-mcp stdio` with a workspace and profile.
96
+ 2. The server negotiates one of the supported MCP versions.
97
+ 3. Tool discovery is generated from the same catalog and policy used by remote mode.
98
+ 4. Each call receives an internal random call ID used only for cancellation and process tracking.
99
+ 5. Results are emitted as JSON-RPC on stdout; logs remain on stderr.
100
+ 6. Duplicate in-flight request IDs are rejected.
101
+ 7. Closing stdin cancels pending calls, terminates active processes, and removes the isolated runtime directory.
102
+
103
+ ## Filesystem resolution and privacy
104
+
105
+ The workspace is canonicalized once. Existing targets use `realpath`; the result must remain inside the workspace unless unrestricted mode is explicit. New targets walk to the nearest existing ancestor and validate its canonical path.
106
+
107
+ Path behavior is profile-dependent. The default `full` profile permits unrestricted direct filesystem paths and returns absolute paths. The `agent`, `edit`, and `review` profiles enforce canonical workspace containment and return workspace-relative paths. Error strings redact canonical and common platform-alias forms of workspace, runtime, and home paths whenever absolute path display is disabled.
108
+
109
+ Symbolic-link destinations and non-regular write targets are rejected. Recursive walkers do not follow symbolic-link directories.
110
+
111
+ ## Mutation model
112
+
113
+ All bridge mutations are serialized in one runtime queue.
114
+
115
+ ### Whole-file write
116
+
117
+ A same-directory temporary file is created with restrictive permissions. Existing mode is preserved when replacing a regular file. Expected hashes are rechecked immediately before commit. Create-only uses a hard-link commit that fails atomically if the destination appeared concurrently.
118
+
119
+ ### Exact edit
120
+
121
+ The current UTF-8 content is hashed. The old fragment must exist and, unless `replace_all` is set, occur exactly once. The resulting file is bounded and committed only if the original hash is unchanged.
122
+
123
+ ### Patch transaction
124
+
125
+ The patch parser accepts a strict Begin/End envelope with add, update, move, and delete operations. It rejects malformed hunks, absent or ambiguous context, duplicate textual paths, and different paths that resolve to the same filesystem location.
126
+
127
+ Before commit, all sources and targets are resolved and validated, updated content is computed, and temporary files are staged. Source hashes and destination absence are rechecked. Existing sources are renamed to backups, staged files are committed, and any failure rolls back committed operations in reverse order. Backups are deleted only after full success.
56
128
 
57
- Browser requests are same-origin unless an exact origin is configured. Configured origins receive bounded CORS preflight and response headers; loopback OAuth callback permission is evaluated separately and never acts as a browser-origin wildcard.
129
+ This is a process-level transaction, not a filesystem-wide atomic transaction across multiple directories. Sudden power loss can still interrupt a multi-file commit; retained backup/temp naming makes recovery identifiable.
58
130
 
59
- ## Filesystem resolution
131
+ ## Process model
60
132
 
61
- The workspace is canonicalized at daemon construction. Existing read/list/search/Git targets are canonicalized with `realpath`; the canonical result must remain inside the workspace unless unrestricted mode is explicit. New write targets walk to the nearest existing ancestor and validate that ancestor, preventing a missing-path write through an escaping symbolic-link directory.
133
+ `run_process` and process sessions use argv arrays and do not invoke a shell. `exec_command` invokes the platform shell and is available only in `shell` mode. Both inherit local-user OS authority.
62
134
 
63
- Writes use a same-directory temporary file and rename, preserving the existing mode when replacing a regular file. Bridge Git commands disable repository-configured external diff, text conversion, and filesystem-monitor hooks to avoid executing repository-local helpers during status/diff inspection. Symbolic-link destinations and non-regular targets are rejected.
135
+ The default `full` profile passes the complete parent environment. Isolated environment mode, used by the narrower named profiles unless overridden, creates private runtime HOME, temporary, and cache directories and passes only a small set of path/locale/platform variables. It reduces accidental credential inheritance but cannot prevent explicit access to known filesystem paths, credential stores, network services, or other user resources.
64
136
 
65
- ## Reconnect and replacement
137
+ One-shot calls have bounded output and timeouts. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Session IDs are random. Sessions are memory-only and are killed on runtime stop, remote disconnect, or daemon replacement.
66
138
 
67
- The daemon sends heartbeats and reconnects after a close using bounded exponential backoff with jitter. The handshake advertises only enabled tools and policy; it does not transmit a workspace path/name/hash or stable daemon identifier. Closing the current relay socket terminates active child processes so disconnected callers cannot leave long-running commands behind. The Worker permits one active daemon. A replacement remains a bounded candidate until it completes the authenticated `hello` handshake; its deadline is enforced by a Durable Object alarm that survives WebSocket hibernation; only then is the older active socket closed. Pending calls retain their originating socket reference. Closing an old socket rejects only calls assigned to that socket and cannot affect calls sent to the replacement. Active child processes associated with a lost/replaced connection receive termination with forced escalation when needed.
139
+ Child processes run in a separate process group where supported. Timeout, cancellation, disconnect, and replacement send termination to the process tree, with forced escalation for timeout/disconnect paths.
68
140
 
69
- ## Limits
141
+ ## Daemon reconnect and replacement
70
142
 
71
- Limits are defense-in-depth and are intentionally below platform maxima:
143
+ The daemon sends heartbeats and reconnects with bounded exponential backoff and jitter. Only one socket is active. A new connection is a bounded candidate until it authenticates and completes `hello`; a Durable Object alarm enforces the deadline across hibernation. Only a completed candidate replaces the old socket.
72
144
 
73
- - Worker MCP body: configurable, default 8 MiB, hard cap 16 MiB.
74
- - OAuth body: 64 KiB.
75
- - Local WebSocket message: 8 MiB.
76
- - `write_file` content: 5 MiB.
77
- - `exec_command` command text: 64 KiB.
78
- - Direct directory results: 10,000 entries and 4 MiB of path metadata.
79
- - Recursive walks: 200,000 visited entries; path-list results are capped at 4 MiB.
80
- - Captured process output: 512 KiB per stream by default.
81
- - Local simultaneous tool calls: 16.
82
- - Worker pending daemon calls: 32.
83
- - Command timeout: 1-600 seconds.
84
- - Dynamic clients, redirect URIs, codes, tokens, failed login identities, and per-client records: bounded constants in Worker source.
145
+ Pending calls retain their originating socket reference. A stale socket cannot complete or cancel replacement-socket calls. Closing a socket rejects only calls assigned to it.
85
146
 
86
147
  ## Persistence
87
148
 
88
- The local state schema is versioned. A custom state root is adopted only when empty or when it contains a recognizable legacy layout; legacy text markers are migrated, and removal validates the marker, contents, canonical path, active locks, and workspace/source-tree exclusions. JSON state and global config are written to owner-only temporary files, flushed, and atomically renamed. A malformed state file is renamed to a bounded corrupt backup before a clean state object is reconstructed. State roots carry an owner-only marker; uninstall validates the target and refuses dangerous or unrecognized recursive deletion.
149
+ Local state and global config are owner-only, versioned, and written through temporary files, flushes, and atomic rename. Malformed state becomes a bounded `.corrupt-*` backup. Custom roots are adopted only when empty or recognizable as legacy Machine Bridge state.
89
150
 
90
- OAuth metadata is pruned on access. Expired codes and tokens, old throttling records, and inactive clients without active credentials are removed. Registration and password-throttling identities use a deployment-keyed HMAC of the Cloudflare source address rather than storing an address or reversible unsalted hash.
151
+ Removal validates the state marker, canonical target, known contents, active locks, filesystem root/home/current/package/workspace/source exclusions, and Worker deletion outcome before recursive deletion.
91
152
 
92
- Cross-origin browser access is denied unless the exact origin is configured; allowed origins receive bounded preflight and response headers. Same-origin requests remain allowed.
153
+ OAuth metadata is pruned on access. Expired codes/tokens, old throttling records, and inactive clients without active credentials are removed. Source identities are deployment-keyed HMAC values, not stored source addresses or reversible unsalted hashes.
93
154
 
94
- ## Observability and privacy
155
+ ## Observability
95
156
 
96
- Public health output contains only server identity and version. Worker observability uses a 10% head-sampling rate. Live daemon details are available through the authenticated `server_info` tool. Local structured logging redacts sensitive key names and known credential formats, bounds nested values, handles cycles, and escapes control characters. Doctor output does not echo Wrangler account identity when authentication succeeds.
157
+ Public health exposes only server identity and version. Authenticated `server_info` exposes bounded runtime status. Local operational logs record tool identity, shortened random call ID, duration, outcome, and coarse error class, but not arguments or outputs. Worker unexpected-error logs contain endpoint and error class, not raw exception text.
97
158
 
98
- Service logs are owner-only where supported and are tail-trimmed before daemon startup. Cloudflare observability sampling defaults to 10% rather than retaining every invocation. This is size control, not an audit log. The project deliberately does not log file contents, command strings, OAuth passwords, access tokens, or daemon secrets as structured operational events.
159
+ Cloudflare sampling is size control rather than an audit log. The project intentionally does not claim complete forensic logging.
99
160
 
100
- ## Removed local API
161
+ ## Explicit non-goals
101
162
 
102
- The experimental local OpenAI-compatible `/v1` API and MCP sampling proxy were removed. They introduced a second local request surface and depended on client-side sampling behavior that was not a reliable backend contract. The supported interface is the authenticated Remote MCP endpoint.
163
+ - operating-system sandboxing of arbitrary executables;
164
+ - preventing an authorized client from requesting data available to enabled tools;
165
+ - automatically deciding which workspace files are sensitive;
166
+ - surviving daemon restart with process sessions;
167
+ - PTY/terminal emulation;
168
+ - model-level prompt-injection prevention;
169
+ - multi-user tenancy in one Worker deployment.
@@ -0,0 +1,125 @@
1
+ # Client configuration and model ownership
2
+
3
+ ## The central distinction
4
+
5
+ An MCP server is not a model provider. It exposes tools, resources, or prompts. The MCP host is the AI application that owns the conversation, chooses or supplies the model, decides when a tool should be called, sends the tool request, and adds the result back to the model context.
6
+
7
+ For this project:
8
+
9
+ ```text
10
+ Claude Desktop / Cursor / Codex / ChatGPT Desktop / another MCP host
11
+ owns the model, account, conversation, approvals, and tool-selection loop
12
+ |
13
+ | stdio or Streamable HTTP
14
+ v
15
+ machine-bridge-mcp
16
+ exposes local file, Git, image, patch, and process tools
17
+ ```
18
+
19
+ A local stdio connection does **not** use the model running in a separate ChatGPT web conversation. It uses whatever model/session the local host is configured to use:
20
+
21
+ - Claude Desktop uses the Claude account/model available to that application.
22
+ - Cursor uses the model/provider configured in Cursor.
23
+ - Codex CLI and the Codex IDE integration use their own authenticated Codex/OpenAI configuration.
24
+ - ChatGPT Desktop can use its signed-in ChatGPT/Codex host session and can launch local stdio servers on supported hosts.
25
+
26
+ The MCP protocol itself intentionally does not dictate how the host obtains an LLM. The optional MCP sampling primitive can let a server ask a capable host for a completion, but `machine-bridge-mcp` does not depend on sampling and does not bundle a model SDK.
27
+
28
+ ## Why keep stdio when coding clients already have tools?
29
+
30
+ For many users, the client's native filesystem, patch, terminal, and approval system is sufficient. In that case, adding Machine Bridge over stdio is redundant and should be skipped.
31
+
32
+ The stdio mode exists for narrower cases:
33
+
34
+ 1. **One tool contract across several clients.** The same tool names, schemas, patch behavior, Git handling, process sessions, result formats, and logging work in Claude Desktop, Cursor, Codex, ChatGPT Desktop, and other MCP hosts.
35
+ 2. **Transport parity.** The local client can use essentially the same Machine Bridge runtime that remote ChatGPT access uses, without maintaining a second implementation.
36
+ 3. **No remote relay for same-machine use.** The host launches the server directly, so no Cloudflare Worker, public URL, OAuth flow, or network round trip is needed.
37
+ 4. **Capabilities missing from a particular host.** Exact edit semantics, transactional multi-file patches, retained process sessions, image results, or the project's policy profiles may be useful even when the host has basic shell tools.
38
+ 5. **Testing and interoperability.** stdio is a standard MCP transport and provides a direct way to validate the runtime independently of the Worker.
39
+
40
+ It is therefore an optional compatibility and reuse surface, not a replacement for the native agent tooling in Claude Desktop, Cursor, or Codex.
41
+
42
+ ## Choosing a transport
43
+
44
+ | Situation | Recommended path |
45
+ |---|---|
46
+ | ChatGPT web or another service must reach this computer remotely | Remote Worker with HTTPS/OAuth |
47
+ | A local MCP host needs Machine Bridge-specific tools | stdio |
48
+ | The local coding client already provides everything needed | Use the client's native tools; do not configure Machine Bridge stdio |
49
+ | Several local clients should share identical tool semantics | stdio |
50
+ | Access is required from another device or hosted agent | Remote Worker |
51
+
52
+ The MCP specification defines stdio and Streamable HTTP as standard transports. In stdio, the host launches the server as a subprocess and exchanges newline-delimited JSON-RPC through stdin/stdout. Streamable HTTP runs the server independently behind an HTTP endpoint.
53
+
54
+ ## Generate local configuration
55
+
56
+ The default profile is `full`:
57
+
58
+ ```sh
59
+ machine-mcp client-config --client all --workspace /path/to/project
60
+ ```
61
+
62
+ The generated command uses the current Node executable and installed entry script as absolute paths so it does not depend on a GUI application's `PATH`.
63
+
64
+ To reduce authority:
65
+
66
+ ```sh
67
+ machine-mcp client-config --client all --workspace /path/to/project --profile agent
68
+ machine-mcp client-config --client all --workspace /path/to/project --profile edit
69
+ machine-mcp client-config --client all --workspace /path/to/project --profile review
70
+ ```
71
+
72
+ ## JSON stdio configuration
73
+
74
+ Claude Desktop, Cursor, and many generic clients use a shape similar to:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "machine-bridge": {
80
+ "command": "/absolute/path/to/node",
81
+ "args": [
82
+ "/absolute/path/to/machine-mcp.mjs",
83
+ "stdio",
84
+ "--workspace",
85
+ "/path/to/project",
86
+ "--profile",
87
+ "full"
88
+ ]
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ Use the exact output of `client-config`; application-specific configuration locations and UI flows can change.
95
+
96
+ ## Codex configuration
97
+
98
+ Codex supports both local stdio servers and Streamable HTTP servers. A local configuration is:
99
+
100
+ ```toml
101
+ [mcp_servers.machine_bridge]
102
+ command = "/absolute/path/to/node"
103
+ args = ["/absolute/path/to/machine-mcp.mjs", "stdio", "--workspace", "/path/to/project", "--profile", "full"]
104
+ ```
105
+
106
+ Codex CLI, the Codex IDE extension, and supported ChatGPT Desktop Codex hosts can share MCP configuration on the same Codex host. ChatGPT web does not read local Codex configuration files; web use requires a hosted plugin/connector or a reachable remote MCP endpoint.
107
+
108
+ ## Remote ChatGPT connection
109
+
110
+ Run:
111
+
112
+ ```sh
113
+ machine-mcp --workspace /path/to/project
114
+ ```
115
+
116
+ Enter the printed `/mcp` URL in the remote MCP connector. During OAuth authorization, verify the displayed client name and redirect URI before entering the connection password.
117
+
118
+ ## Profile guidance
119
+
120
+ - `full` is the default and prioritizes immediate usability. It exposes shell execution, unrestricted direct filesystem paths, absolute path output, and the full parent environment.
121
+ - `agent` retains file mutation and direct process execution but removes shell parsing, confines direct filesystem tools to the workspace, and isolates the process environment.
122
+ - `edit` permits deterministic file mutation without process execution.
123
+ - `review` is read-only and workspace-confined.
124
+
125
+ A client configuration is an authorization decision. Anyone who controls an authorized host can invoke every tool exposed by the selected profile.