machine-bridge-mcp 0.2.5 → 0.3.3

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.
@@ -0,0 +1,102 @@
1
+ # Architecture
2
+
3
+ ## Components
4
+
5
+ ### CLI
6
+
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.
8
+
9
+ ### Cloudflare Worker and Durable Object
10
+
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:
12
+
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.
16
+
17
+ The daemon attachment deliberately omits the local workspace name, path hash, and process ID.
18
+
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.
20
+
21
+ ### Local daemon
22
+
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.
24
+
25
+ ### Autostart provider
26
+
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.
28
+
29
+ ## Trust boundaries
30
+
31
+ ```mermaid
32
+ 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]
41
+ ```
42
+
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.
44
+
45
+ ## Request lifecycle
46
+
47
+ 1. The MCP client discovers OAuth and protected-resource metadata.
48
+ 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.
56
+
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.
58
+
59
+ ## Filesystem resolution
60
+
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.
62
+
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.
64
+
65
+ ## Reconnect and replacement
66
+
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.
68
+
69
+ ## Limits
70
+
71
+ Limits are defense-in-depth and are intentionally below platform maxima:
72
+
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.
85
+
86
+ ## Persistence
87
+
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.
89
+
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.
91
+
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.
93
+
94
+ ## Observability and privacy
95
+
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.
97
+
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.
99
+
100
+ ## Removed local API
101
+
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.
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "0.2.5",
3
+ "version": "0.3.3",
4
4
  "description": "One-command hosted Remote MCP bridge to a local machine daemon.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "engines": {
8
- "node": ">=20.0.0"
8
+ "node": ">=22.0.0"
9
9
  },
10
10
  "bin": {
11
11
  "machine-bridge-mcp": "bin/machine-mcp.mjs",
@@ -18,10 +18,13 @@
18
18
  "bin",
19
19
  "src/local",
20
20
  "src/worker/index.ts",
21
- "scripts",
21
+ "scripts/sync-worker-version.mjs",
22
22
  "wrangler.jsonc",
23
23
  "tsconfig.json",
24
24
  "README.md",
25
+ "SECURITY.md",
26
+ "CHANGELOG.md",
27
+ "docs",
25
28
  "LICENSE"
26
29
  ],
27
30
  "scripts": {
@@ -35,16 +38,18 @@
35
38
  "prepublishOnly": "npm run check && npm run version:check",
36
39
  "worker:types": "wrangler types src/worker/worker-configuration.d.ts",
37
40
  "typecheck": "npm run worker:types && tsc -p tsconfig.json --noEmit",
38
- "syntax": "sh -n mbm && node --check bin/machine-mcp.mjs && node --check src/local/cli.mjs && node --check src/local/daemon.mjs && node --check src/local/state.mjs && node --check src/local/shell.mjs && node --check src/local/service.mjs && node --check src/local/self-test.mjs && node --check src/local/api-server.mjs && node --check src/local/log.mjs",
39
- "check": "npm run typecheck && npm run syntax && npm run self-test"
41
+ "syntax": "sh -n mbm && node --check bin/machine-mcp.mjs && node --check src/local/cli.mjs && node --check src/local/daemon.mjs && node --check src/local/state.mjs && node --check src/local/shell.mjs && node --check src/local/service.mjs && node --check src/local/self-test.mjs && node --check src/local/log.mjs && node --check scripts/sync-worker-version.mjs && node --check scripts/worker-integration-test.mjs",
42
+ "check": "npm run typecheck && npm run syntax && npm run self-test && npm run worker:integration-test",
43
+ "worker:dry-run": "wrangler deploy --dry-run",
44
+ "worker:integration-test": "node scripts/worker-integration-test.mjs"
40
45
  },
41
46
  "dependencies": {
42
- "wrangler": "^4.24.3",
47
+ "wrangler": "^4.110.0",
43
48
  "ws": "^8.18.3"
44
49
  },
45
50
  "devDependencies": {
46
- "@types/node": "^26.1.1",
47
- "typescript": "^5.8.3"
51
+ "@types/node": "^22.20.1",
52
+ "typescript": "^5.9.3"
48
53
  },
49
54
  "keywords": [
50
55
  "mcp",
@@ -61,5 +66,11 @@
61
66
  "bugs": {
62
67
  "url": "https://github.com/YuLeiFuYun/machine-bridge-mcp/issues"
63
68
  },
64
- "homepage": "https://github.com/YuLeiFuYun/machine-bridge-mcp#readme"
69
+ "homepage": "https://github.com/YuLeiFuYun/machine-bridge-mcp#readme",
70
+ "allowScripts": {
71
+ "esbuild@0.28.1": true,
72
+ "sharp@0.34.5": true,
73
+ "workerd@1.20260708.1": true,
74
+ "fsevents": false
75
+ }
65
76
  }