machine-bridge-mcp 0.6.0 → 0.6.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/CHANGELOG.md +41 -0
- package/README.md +21 -4
- package/SECURITY.md +10 -4
- package/docs/ARCHITECTURE.md +10 -2
- package/docs/CLIENTS.md +11 -1
- package/docs/LOGGING.md +9 -18
- package/docs/MANAGED_JOBS.md +8 -0
- package/docs/OPERATIONS.md +24 -6
- package/docs/TESTING.md +5 -1
- package/package.json +12 -6
- package/src/local/atomic-fs.mjs +37 -0
- package/src/local/cli.mjs +63 -3
- package/src/local/daemon.mjs +67 -5
- package/src/local/full-access-test.mjs +206 -0
- package/src/local/job-runner.mjs +3 -2
- package/src/local/managed-jobs.mjs +3 -2
- package/src/local/resource-operations.mjs +66 -0
- package/src/local/ssh-key.mjs +125 -0
- package/src/local/state.mjs +5 -4
- package/src/local/stdio.mjs +2 -4
- package/src/local/tools.mjs +37 -3
- package/src/shared/server-metadata.json +3 -1
- package/src/shared/tool-catalog.json +33 -0
- package/src/worker/index.ts +7 -1
package/src/local/tools.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export const MCP_INSTRUCTIONS = Object.freeze(serverMetadata.instructions.map((v
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
export const DEFAULT_POLICY_PROFILE = "full";
|
|
12
|
-
export const DEFAULT_POLICY_REVISION =
|
|
12
|
+
export const DEFAULT_POLICY_REVISION = 3;
|
|
13
13
|
|
|
14
14
|
export const POLICY_PROFILES = Object.freeze({
|
|
15
15
|
review: Object.freeze({ profile: "review", allowWrite: false, execMode: "off", unrestrictedPaths: false, minimalEnv: true, exposeAbsolutePaths: false }),
|
|
@@ -34,8 +34,9 @@ export function normalizePolicy(policy = {}) {
|
|
|
34
34
|
: "off";
|
|
35
35
|
const origin = POLICY_ORIGINS.has(policy.origin) ? policy.origin : "custom";
|
|
36
36
|
const revision = Number.isInteger(policy.revision) && policy.revision > 0 ? policy.revision : DEFAULT_POLICY_REVISION;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const requestedProfile = typeof policy.profile === "string" && policy.profile ? policy.profile : "custom";
|
|
38
|
+
const normalized = {
|
|
39
|
+
profile: requestedProfile,
|
|
39
40
|
origin,
|
|
40
41
|
revision,
|
|
41
42
|
allowWrite: policy.allowWrite === true,
|
|
@@ -45,6 +46,38 @@ export function normalizePolicy(policy = {}) {
|
|
|
45
46
|
minimalEnv: policy.minimalEnv !== false,
|
|
46
47
|
exposeAbsolutePaths: policy.exposeAbsolutePaths === true,
|
|
47
48
|
};
|
|
49
|
+
if (POLICY_PROFILES[requestedProfile]) {
|
|
50
|
+
const canonical = POLICY_PROFILES[requestedProfile];
|
|
51
|
+
normalized.allowWrite = canonical.allowWrite;
|
|
52
|
+
normalized.allowExec = canonical.execMode !== "off";
|
|
53
|
+
normalized.execMode = canonical.execMode;
|
|
54
|
+
normalized.unrestrictedPaths = canonical.unrestrictedPaths;
|
|
55
|
+
normalized.minimalEnv = canonical.minimalEnv;
|
|
56
|
+
normalized.exposeAbsolutePaths = canonical.exposeAbsolutePaths;
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isCanonicalFullPolicy(policy = {}) {
|
|
62
|
+
return policyCapabilitiesEqual(normalizePolicy(policy), POLICY_PROFILES.full);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function assertCanonicalFullPolicy(policy = {}) {
|
|
66
|
+
const normalized = normalizePolicy(policy);
|
|
67
|
+
if (!isCanonicalFullPolicy(normalized) || normalized.profile !== "full") {
|
|
68
|
+
throw new Error("full profile invariant failed: full must enable writes, shell execution, unrestricted paths, full parent environment, and absolute paths");
|
|
69
|
+
}
|
|
70
|
+
const exposed = toolsForPolicy(normalized);
|
|
71
|
+
if (exposed.length !== catalog.length) throw new Error("full profile invariant failed: complete tool catalog is not exposed");
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function policyCapabilitiesEqual(left, right) {
|
|
76
|
+
return left.allowWrite === right.allowWrite
|
|
77
|
+
&& left.execMode === right.execMode
|
|
78
|
+
&& left.unrestrictedPaths === right.unrestrictedPaths
|
|
79
|
+
&& left.minimalEnv === right.minimalEnv
|
|
80
|
+
&& left.exposeAbsolutePaths === right.exposeAbsolutePaths;
|
|
48
81
|
}
|
|
49
82
|
|
|
50
83
|
export function toolsForPolicy(policy = {}) {
|
|
@@ -92,6 +125,7 @@ function isAvailable(availability, policy) {
|
|
|
92
125
|
if (availability === "write") return policy.allowWrite;
|
|
93
126
|
if (availability === "direct-exec") return policy.execMode === "direct" || policy.execMode === "shell";
|
|
94
127
|
if (availability === "shell-exec") return policy.execMode === "shell";
|
|
128
|
+
if (availability === "full") return policy.profile === "full" && isCanonicalFullPolicy(policy);
|
|
95
129
|
return false;
|
|
96
130
|
}
|
|
97
131
|
|
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
"Remote mode uses a Cloudflare relay; stdio mode runs on the local machine. File and command operations execute on the user's local runtime, not in the Worker.",
|
|
12
12
|
"Filesystem scope and path display follow the active local policy; the default full profile is unrestricted.",
|
|
13
13
|
"Filename sensitivity is not classified by this server; the MCP host, connector gateway, local OS, or endpoint-security software may enforce additional independent rules.",
|
|
14
|
+
"A named full profile is canonical: it always enables writes, shell execution, unrestricted paths, the full parent environment, absolute paths, and the complete tool catalog. Any explicit narrowing is stored as custom.",
|
|
14
15
|
"Use diagnose_runtime to distinguish a request that reached the daemon from local filesystem, process-spawn, shell, managed-job-storage, and resource failures. A tool call blocked before any response cannot be diagnosed by the server.",
|
|
15
|
-
"Never request or return secret-file contents when a local resource alias can be used. Resources are registered
|
|
16
|
+
"Never request or return secret-file contents when a local resource alias can be used. Resources are registered through the local machine-mcp CLI; under canonical full policy, generate_ssh_key_resource can generate and register an Ed25519 key without returning private content and may be injected by path, stdin, or environment without entering MCP arguments.",
|
|
16
17
|
"If execution-class tools are unavailable but write access remains, use stage_job to persist a reviewed plan without running it; the operator can launch it locally with machine-mcp job approve JOB_ID.",
|
|
17
18
|
"For multi-step, remote, long-running, or cleanup-sensitive work, prefer one start_job call with argv steps, job-scoped temporary_files, and idempotent finally_steps. The detached runner continues after MCP disconnects; inspect it with read_job or the local machine-mcp job CLI.",
|
|
18
19
|
"Prefer sending remote shell programs through a process stdin rather than creating remote helper files. If temporary files are necessary, use {{temp:name}} or explicit finally_steps.",
|
|
19
20
|
"Managed-job resource redaction is defense in depth, not a guarantee against transformed or partial secret output. Use capture_output=discard for steps that may echo credentials.",
|
|
20
21
|
"Do not use shell encoding, renaming, or alternate tools to bypass host or platform safety policy.",
|
|
21
22
|
"run_process avoids shell parsing but is not an OS sandbox. exec_command is exposed only in shell mode and has the local user's authority.",
|
|
23
|
+
"Per-tool success, failure, cancellation, and timing events are debug-only; default logs contain lifecycle and infrastructure events, not a tool-call transcript.",
|
|
22
24
|
"Inspect before editing, prefer read_file line ranges, edit_file, or apply_patch over whole-file replacement, and report commands that were run."
|
|
23
25
|
]
|
|
24
26
|
}
|
|
@@ -635,6 +635,39 @@
|
|
|
635
635
|
"additionalProperties": false
|
|
636
636
|
}
|
|
637
637
|
},
|
|
638
|
+
{
|
|
639
|
+
"name": "generate_ssh_key_resource",
|
|
640
|
+
"title": "Generate SSH key resource",
|
|
641
|
+
"description": "Generate or reuse an Ed25519 SSH key pair on the local machine and register the private key file as a local resource alias. The private key content is never returned; only local paths, public fingerprint, permissions, and registration status are reported. Available only under canonical full policy.",
|
|
642
|
+
"availability": "full",
|
|
643
|
+
"annotations": {
|
|
644
|
+
"readOnlyHint": false,
|
|
645
|
+
"destructiveHint": false,
|
|
646
|
+
"idempotentHint": true,
|
|
647
|
+
"openWorldHint": false
|
|
648
|
+
},
|
|
649
|
+
"inputSchema": {
|
|
650
|
+
"type": "object",
|
|
651
|
+
"properties": {
|
|
652
|
+
"name": {
|
|
653
|
+
"type": "string",
|
|
654
|
+
"pattern": "^[a-z][a-z0-9._-]{0,63}$"
|
|
655
|
+
},
|
|
656
|
+
"path": {
|
|
657
|
+
"type": "string",
|
|
658
|
+
"maxLength": 4096
|
|
659
|
+
},
|
|
660
|
+
"comment": {
|
|
661
|
+
"type": "string",
|
|
662
|
+
"maxLength": 256
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
"required": [
|
|
666
|
+
"name"
|
|
667
|
+
],
|
|
668
|
+
"additionalProperties": false
|
|
669
|
+
}
|
|
670
|
+
},
|
|
638
671
|
{
|
|
639
672
|
"name": "stage_job",
|
|
640
673
|
"title": "Stage managed job for local approval",
|
package/src/worker/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import toolCatalog from "../shared/tool-catalog.json";
|
|
|
3
3
|
import serverMetadata from "../shared/server-metadata.json";
|
|
4
4
|
|
|
5
5
|
const SERVER_NAME = String(serverMetadata.name);
|
|
6
|
-
const SERVER_VERSION = "0.6.
|
|
6
|
+
const SERVER_VERSION = "0.6.2";
|
|
7
7
|
const MCP_PROTOCOL_VERSION = String(serverMetadata.protocolVersion);
|
|
8
8
|
const MCP_SUPPORTED_PROTOCOL_VERSIONS = serverMetadata.supportedProtocolVersions.map((value) => String(value));
|
|
9
9
|
const JSONRPC_VERSION = "2.0";
|
|
@@ -991,6 +991,12 @@ function daemonPolicyAllows(availability: unknown, policy: Record<string, unknow
|
|
|
991
991
|
if (availability === "write") return policy.allowWrite === true;
|
|
992
992
|
if (availability === "direct-exec") return policy.execMode === "direct" || policy.execMode === "shell";
|
|
993
993
|
if (availability === "shell-exec") return policy.execMode === "shell";
|
|
994
|
+
if (availability === "full") return policy.profile === "full"
|
|
995
|
+
&& policy.allowWrite === true
|
|
996
|
+
&& policy.execMode === "shell"
|
|
997
|
+
&& policy.unrestrictedPaths === true
|
|
998
|
+
&& policy.minimalEnv === false
|
|
999
|
+
&& policy.exposeAbsolutePaths === true;
|
|
994
1000
|
return false;
|
|
995
1001
|
}
|
|
996
1002
|
|