host-execution-mcp 1.0.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 +330 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/SECURITY.md +72 -0
- package/dist/access/docker-runtime.d.ts +38 -0
- package/dist/access/docker-runtime.js +61 -0
- package/dist/access/docker-runtime.js.map +1 -0
- package/dist/access/index.d.ts +110 -0
- package/dist/access/index.js +174 -0
- package/dist/access/index.js.map +1 -0
- package/dist/access/paradi-auth.d.ts +33 -0
- package/dist/access/paradi-auth.js +89 -0
- package/dist/access/paradi-auth.js.map +1 -0
- package/dist/access/paradi-transport.d.ts +10 -0
- package/dist/access/paradi-transport.js +39 -0
- package/dist/access/paradi-transport.js.map +1 -0
- package/dist/access/paradi-username.d.ts +5 -0
- package/dist/access/paradi-username.js +14 -0
- package/dist/access/paradi-username.js.map +1 -0
- package/dist/access/probe.d.ts +14 -0
- package/dist/access/probe.js +63 -0
- package/dist/access/probe.js.map +1 -0
- package/dist/access/session-pool.d.ts +25 -0
- package/dist/access/session-pool.js +46 -0
- package/dist/access/session-pool.js.map +1 -0
- package/dist/access/ssh-transport.d.ts +16 -0
- package/dist/access/ssh-transport.js +74 -0
- package/dist/access/ssh-transport.js.map +1 -0
- package/dist/access/types.d.ts +43 -0
- package/dist/access/types.js +21 -0
- package/dist/access/types.js.map +1 -0
- package/dist/config.d.ts +52 -0
- package/dist/config.js +179 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +4 -0
- package/dist/instructions.js +93 -0
- package/dist/instructions.js.map +1 -0
- package/dist/kubectl/runner.d.ts +32 -0
- package/dist/kubectl/runner.js +79 -0
- package/dist/kubectl/runner.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +24 -0
- package/dist/server.js.map +1 -0
- package/dist/ssh/access-client.d.ts +62 -0
- package/dist/ssh/access-client.js +470 -0
- package/dist/ssh/access-client.js.map +1 -0
- package/dist/ssh/access-errors.d.ts +53 -0
- package/dist/ssh/access-errors.js +180 -0
- package/dist/ssh/access-errors.js.map +1 -0
- package/dist/ssh/access-sftp.d.ts +14 -0
- package/dist/ssh/access-sftp.js +456 -0
- package/dist/ssh/access-sftp.js.map +1 -0
- package/dist/ssh/bastion-client.d.ts +60 -0
- package/dist/ssh/bastion-client.js +423 -0
- package/dist/ssh/bastion-client.js.map +1 -0
- package/dist/ssh/bastion-errors.d.ts +62 -0
- package/dist/ssh/bastion-errors.js +190 -0
- package/dist/ssh/bastion-errors.js.map +1 -0
- package/dist/ssh/hop1-sftp.d.ts +14 -0
- package/dist/ssh/hop1-sftp.js +437 -0
- package/dist/ssh/hop1-sftp.js.map +1 -0
- package/dist/tools/copy-job-schemas.d.ts +709 -0
- package/dist/tools/copy-job-schemas.js +48 -0
- package/dist/tools/copy-job-schemas.js.map +1 -0
- package/dist/tools/register.d.ts +14 -0
- package/dist/tools/register.js +619 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/remote-schemas.d.ts +879 -0
- package/dist/tools/remote-schemas.js +214 -0
- package/dist/tools/remote-schemas.js.map +1 -0
- package/dist/utils/copy-executor.d.ts +41 -0
- package/dist/utils/copy-executor.js +231 -0
- package/dist/utils/copy-executor.js.map +1 -0
- package/dist/utils/copy-job-manager.d.ts +34 -0
- package/dist/utils/copy-job-manager.js +400 -0
- package/dist/utils/copy-job-manager.js.map +1 -0
- package/dist/utils/copy-job-store.d.ts +18 -0
- package/dist/utils/copy-job-store.js +104 -0
- package/dist/utils/copy-job-store.js.map +1 -0
- package/dist/utils/copy-job-types.d.ts +133 -0
- package/dist/utils/copy-job-types.js +37 -0
- package/dist/utils/copy-job-types.js.map +1 -0
- package/dist/utils/copy-transfer.d.ts +59 -0
- package/dist/utils/copy-transfer.js +86 -0
- package/dist/utils/copy-transfer.js.map +1 -0
- package/dist/utils/remote-copy.d.ts +14 -0
- package/dist/utils/remote-copy.js +115 -0
- package/dist/utils/remote-copy.js.map +1 -0
- package/dist/utils/remote-executor.d.ts +15 -0
- package/dist/utils/remote-executor.js +34 -0
- package/dist/utils/remote-executor.js.map +1 -0
- package/dist/utils/remote-target.d.ts +22 -0
- package/dist/utils/remote-target.js +75 -0
- package/dist/utils/remote-target.js.map +1 -0
- package/dist/utils/result.d.ts +87 -0
- package/dist/utils/result.js +81 -0
- package/dist/utils/result.js.map +1 -0
- package/dist/utils/shell-quote.d.ts +22 -0
- package/dist/utils/shell-quote.js +55 -0
- package/dist/utils/shell-quote.js.map +1 -0
- package/dist/utils/timeout.d.ts +1 -0
- package/dist/utils/timeout.js +18 -0
- package/dist/utils/timeout.js.map +1 -0
- package/dist/utils/truncate.d.ts +5 -0
- package/dist/utils/truncate.js +12 -0
- package/dist/utils/truncate.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/docs/AGENT_GUIDE.md +161 -0
- package/docs/CONFIGURATION.md +130 -0
- package/docs/COPY_SPECIALIZED_TEST_CHECKLIST.md +9 -0
- package/docs/HOP1_E2E_CHECKLIST.md +128 -0
- package/docs/HOP1_E2E_RESULTS.json +647 -0
- package/docs/PARADI_ACCESS_PLAN.md +272 -0
- package/docs/REMOTE_COPY_JOB_API.md +92 -0
- package/docs/TODO.md +66 -0
- package/docs/USER_GUIDE.zh-CN.md +93 -0
- package/docs/clients/claude-desktop.json.example +12 -0
- package/docs/clients/cursor-npx.json.example +12 -0
- package/docs/clients/cursor.json.example +12 -0
- package/docs/clients/vscode.json.example +12 -0
- package/package.json +65 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { AppConfig } from "../config.js";
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
exitCode: number;
|
|
7
|
+
truncated: boolean;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ReadFileResult extends CommandResult {
|
|
11
|
+
mode?: "head" | "tail";
|
|
12
|
+
totalLines?: number;
|
|
13
|
+
totalBytes?: number;
|
|
14
|
+
returnedLines?: number;
|
|
15
|
+
lines?: number;
|
|
16
|
+
maxLines?: number;
|
|
17
|
+
truncatedByLines?: boolean;
|
|
18
|
+
truncatedByChars?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface CopyFileResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
operation: "copy";
|
|
23
|
+
source: {
|
|
24
|
+
target: string;
|
|
25
|
+
path: string;
|
|
26
|
+
};
|
|
27
|
+
dest: {
|
|
28
|
+
target: string;
|
|
29
|
+
path: string;
|
|
30
|
+
};
|
|
31
|
+
bytes?: number;
|
|
32
|
+
totalBytes?: number;
|
|
33
|
+
skippedBytes?: number;
|
|
34
|
+
skipped?: boolean;
|
|
35
|
+
skipReason?: "size-match" | "hash-match";
|
|
36
|
+
transferId?: string;
|
|
37
|
+
verified?: boolean;
|
|
38
|
+
usedFastPath?: boolean;
|
|
39
|
+
message: string;
|
|
40
|
+
exitCode: number;
|
|
41
|
+
durationMs: number;
|
|
42
|
+
}
|
|
43
|
+
export declare function formatCommandResult(config: AppConfig, raw: {
|
|
44
|
+
stdout: string;
|
|
45
|
+
stderr: string;
|
|
46
|
+
exitCode: number;
|
|
47
|
+
durationMs: number;
|
|
48
|
+
}): CommandResult;
|
|
49
|
+
export declare function formatErrorResult(config: AppConfig, error: unknown, durationMs: number): CommandResult;
|
|
50
|
+
export declare function formatReadFileResult(config: AppConfig, raw: {
|
|
51
|
+
content: string;
|
|
52
|
+
exitCode: number;
|
|
53
|
+
durationMs: number;
|
|
54
|
+
maxLines: number;
|
|
55
|
+
mode?: "head" | "tail";
|
|
56
|
+
meta?: {
|
|
57
|
+
totalLines?: number;
|
|
58
|
+
totalBytes?: number;
|
|
59
|
+
};
|
|
60
|
+
}): ReadFileResult;
|
|
61
|
+
export declare function formatCopyFileResult(raw: {
|
|
62
|
+
source: {
|
|
63
|
+
target: string;
|
|
64
|
+
path: string;
|
|
65
|
+
};
|
|
66
|
+
dest: {
|
|
67
|
+
target: string;
|
|
68
|
+
path: string;
|
|
69
|
+
};
|
|
70
|
+
exitCode: number;
|
|
71
|
+
durationMs: number;
|
|
72
|
+
bytes?: number;
|
|
73
|
+
totalBytes?: number;
|
|
74
|
+
skippedBytes?: number;
|
|
75
|
+
skipped?: boolean;
|
|
76
|
+
skipReason?: "size-match" | "hash-match";
|
|
77
|
+
transferId?: string;
|
|
78
|
+
verified?: boolean;
|
|
79
|
+
usedFastPath?: boolean;
|
|
80
|
+
message: string;
|
|
81
|
+
}): CopyFileResult;
|
|
82
|
+
export declare function toToolText(result: CommandResult | ReadFileResult | CopyFileResult | Record<string, unknown>): {
|
|
83
|
+
content: Array<{
|
|
84
|
+
type: "text";
|
|
85
|
+
text: string;
|
|
86
|
+
}>;
|
|
87
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isAccessUnavailableError } from "../ssh/access-errors.js";
|
|
2
|
+
import { truncateOutput } from "./truncate.js";
|
|
3
|
+
export function formatCommandResult(config, raw) {
|
|
4
|
+
const combined = [raw.stdout, raw.stderr].filter(Boolean).join(raw.stdout && raw.stderr ? "\n" : "");
|
|
5
|
+
const { text, truncated } = truncateOutput(combined, config.maxOutputChars);
|
|
6
|
+
return {
|
|
7
|
+
success: raw.exitCode === 0,
|
|
8
|
+
stdout: text,
|
|
9
|
+
stderr: "",
|
|
10
|
+
exitCode: raw.exitCode,
|
|
11
|
+
truncated,
|
|
12
|
+
durationMs: raw.durationMs,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function formatErrorResult(config, error, durationMs) {
|
|
16
|
+
const message = isAccessUnavailableError(error)
|
|
17
|
+
? JSON.stringify({
|
|
18
|
+
...error.toJSON(),
|
|
19
|
+
// Keep human-readable multiline message for Agents that only read stderr.
|
|
20
|
+
message: error.message,
|
|
21
|
+
}, null, 2)
|
|
22
|
+
: error instanceof Error
|
|
23
|
+
? error.message
|
|
24
|
+
: String(error);
|
|
25
|
+
const { text, truncated } = truncateOutput(message, config.maxOutputChars);
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
stdout: "",
|
|
29
|
+
stderr: text,
|
|
30
|
+
exitCode: 1,
|
|
31
|
+
truncated,
|
|
32
|
+
durationMs,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function formatReadFileResult(config, raw) {
|
|
36
|
+
const base = formatCommandResult(config, {
|
|
37
|
+
stdout: raw.content,
|
|
38
|
+
stderr: "",
|
|
39
|
+
exitCode: raw.exitCode,
|
|
40
|
+
durationMs: raw.durationMs,
|
|
41
|
+
});
|
|
42
|
+
const returnedLines = raw.content ? raw.content.split("\n").length : 0;
|
|
43
|
+
const totalLines = raw.meta?.totalLines;
|
|
44
|
+
const truncatedByLines = totalLines !== undefined && totalLines > raw.maxLines && returnedLines >= raw.maxLines;
|
|
45
|
+
return {
|
|
46
|
+
...base,
|
|
47
|
+
mode: raw.mode,
|
|
48
|
+
totalLines,
|
|
49
|
+
totalBytes: raw.meta?.totalBytes,
|
|
50
|
+
returnedLines,
|
|
51
|
+
lines: raw.maxLines,
|
|
52
|
+
maxLines: raw.maxLines,
|
|
53
|
+
truncatedByLines,
|
|
54
|
+
truncatedByChars: base.truncated,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function formatCopyFileResult(raw) {
|
|
58
|
+
return {
|
|
59
|
+
success: raw.exitCode === 0,
|
|
60
|
+
operation: "copy",
|
|
61
|
+
source: raw.source,
|
|
62
|
+
dest: raw.dest,
|
|
63
|
+
bytes: raw.bytes,
|
|
64
|
+
totalBytes: raw.totalBytes,
|
|
65
|
+
skippedBytes: raw.skippedBytes,
|
|
66
|
+
skipped: raw.skipped,
|
|
67
|
+
skipReason: raw.skipReason,
|
|
68
|
+
transferId: raw.transferId,
|
|
69
|
+
verified: raw.verified,
|
|
70
|
+
usedFastPath: raw.usedFastPath,
|
|
71
|
+
message: raw.message,
|
|
72
|
+
exitCode: raw.exitCode,
|
|
73
|
+
durationMs: raw.durationMs,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function toToolText(result) {
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/utils/result.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a string for safe embedding inside single-quoted bash -lc '...'.
|
|
3
|
+
* Uses the pattern: '...'"'"'...' for embedded single quotes.
|
|
4
|
+
*/
|
|
5
|
+
export declare function bashSingleQuote(value: string): string;
|
|
6
|
+
export declare function buildBashLcCommand(command: string, cwd?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Shared -o flags for Hop2 host ssh/scp.
|
|
9
|
+
* Password auth omits BatchMode so sshpass can prompt-less password auth.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hostSshBaseOpts(password?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional sshpass + ssh|scp + shared -o opts (no target / remote command).
|
|
14
|
+
* Used by buildSshRemoteCommand and scp helpers so opts do not fork.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildHostTransportPrefix(binary: "ssh" | "scp", password?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Bastion → host remote exec: ssh … with the entire `bash -lc '<cmd>'` as one
|
|
19
|
+
* argv so OpenSSH does not space-join and truncate multi-word commands.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildSshRemoteCommand(user: string, host: string, command: string, password?: string): string;
|
|
22
|
+
export declare function shellEscapeArg(value: string): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a string for safe embedding inside single-quoted bash -lc '...'.
|
|
3
|
+
* Uses the pattern: '...'"'"'...' for embedded single quotes.
|
|
4
|
+
*/
|
|
5
|
+
export function bashSingleQuote(value) {
|
|
6
|
+
if (!value.includes("'")) {
|
|
7
|
+
return `'${value}'`;
|
|
8
|
+
}
|
|
9
|
+
const escapedQuote = "'\"'\"'";
|
|
10
|
+
return `'${value.replace(/'/g, escapedQuote)}'`;
|
|
11
|
+
}
|
|
12
|
+
export function buildBashLcCommand(command, cwd) {
|
|
13
|
+
const quoted = bashSingleQuote(command);
|
|
14
|
+
if (cwd) {
|
|
15
|
+
return `cd ${bashSingleQuote(cwd)} && bash -lc ${quoted}`;
|
|
16
|
+
}
|
|
17
|
+
return `bash -lc ${quoted}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Shared -o flags for Hop2 host ssh/scp.
|
|
21
|
+
* Password auth omits BatchMode so sshpass can prompt-less password auth.
|
|
22
|
+
*/
|
|
23
|
+
export function hostSshBaseOpts(password) {
|
|
24
|
+
if (password) {
|
|
25
|
+
return "-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10";
|
|
26
|
+
}
|
|
27
|
+
return "-o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Optional sshpass + ssh|scp + shared -o opts (no target / remote command).
|
|
31
|
+
* Used by buildSshRemoteCommand and scp helpers so opts do not fork.
|
|
32
|
+
*/
|
|
33
|
+
export function buildHostTransportPrefix(binary, password) {
|
|
34
|
+
const opts = hostSshBaseOpts(password);
|
|
35
|
+
if (password) {
|
|
36
|
+
return `sshpass -p ${bashSingleQuote(password)} ${binary} ${opts}`;
|
|
37
|
+
}
|
|
38
|
+
return `${binary} ${opts}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Bastion → host remote exec: ssh … with the entire `bash -lc '<cmd>'` as one
|
|
42
|
+
* argv so OpenSSH does not space-join and truncate multi-word commands.
|
|
43
|
+
*/
|
|
44
|
+
export function buildSshRemoteCommand(user, host, command, password) {
|
|
45
|
+
const target = `${user}@${host}`;
|
|
46
|
+
const remote = `bash -lc ${bashSingleQuote(command)}`;
|
|
47
|
+
return `${buildHostTransportPrefix("ssh", password)} ${bashSingleQuote(target)} ${bashSingleQuote(remote)}`;
|
|
48
|
+
}
|
|
49
|
+
export function shellEscapeArg(value) {
|
|
50
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
return bashSingleQuote(value);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=shell-quote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-quote.js","sourceRoot":"","sources":["../../src/utils/shell-quote.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,KAAK,GAAG,CAAC;IACtB,CAAC;IACD,MAAM,YAAY,GAAG,SAAS,CAAC;IAC/B,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,GAAY;IAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,GAAG,EAAE,CAAC;QACR,OAAO,MAAM,eAAe,CAAC,GAAG,CAAC,gBAAgB,MAAM,EAAE,CAAC;IAC5D,CAAC;IACD,OAAO,YAAY,MAAM,EAAE,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAiB;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,0DAA0D,CAAC;IACpE,CAAC;IACD,OAAO,2EAA2E,CAAC;AACrF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAqB,EAAE,QAAiB;IAC/E,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,cAAc,eAAe,CAAC,QAAQ,CAAC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;IACrE,CAAC;IACD,OAAO,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,IAAY,EACZ,OAAe,EACf,QAAiB;IAEjB,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,YAAY,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;IACtD,OAAO,GAAG,wBAAwB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9G,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, onTimeout?: () => void): Promise<T>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function withTimeout(promise, timeoutMs, onTimeout) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const timer = setTimeout(() => {
|
|
4
|
+
onTimeout?.();
|
|
5
|
+
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
|
|
6
|
+
}, timeoutMs);
|
|
7
|
+
promise
|
|
8
|
+
.then((value) => {
|
|
9
|
+
clearTimeout(timer);
|
|
10
|
+
resolve(value);
|
|
11
|
+
})
|
|
12
|
+
.catch((error) => {
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
reject(error);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/utils/timeout.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CACzB,OAAmB,EACnB,SAAiB,EACjB,SAAsB;IAEtB,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,SAAS,IAAI,CAAC,CAAC,CAAC;QAChE,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,OAAO;aACJ,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YACd,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function truncateOutput(text, maxChars) {
|
|
2
|
+
if (text.length <= maxChars) {
|
|
3
|
+
return { text, truncated: false };
|
|
4
|
+
}
|
|
5
|
+
const suffix = `\n\n...[truncated, ${text.length - maxChars} chars omitted]`;
|
|
6
|
+
const keep = Math.max(0, maxChars - suffix.length);
|
|
7
|
+
return {
|
|
8
|
+
text: text.slice(0, keep) + suffix,
|
|
9
|
+
truncated: true,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=truncate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/utils/truncate.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,QAAgB;IAC3D,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;IACD,MAAM,MAAM,GAAG,sBAAsB,IAAI,CAAC,MAAM,GAAG,QAAQ,iBAAiB,CAAC;IAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,MAAM;QAClC,SAAS,EAAE,IAAI;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION: string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const packagePath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
5
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
6
|
+
export const VERSION = pkg.version;
|
|
7
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;AACxF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAwB,CAAC;AAEjF,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Agent Guide — host-execution-mcp
|
|
2
|
+
|
|
3
|
+
Extended reference for LLM agents. A shorter version is injected as MCP server `instructions` at handshake (KIND-aware for `ssh` vs `paradi-gateway`).
|
|
4
|
+
|
|
5
|
+
## Naming (5.0.0)
|
|
6
|
+
|
|
7
|
+
Agent-facing tools use ACCESS/Context naming: `access_check`, `shell_exec`, `context_derive`, `contextKey`, `target=landing|pod|context`. Error code: `ACCESS_UNAVAILABLE`.
|
|
8
|
+
|
|
9
|
+
| ACCESS_KIND | Landing Context | `shell_exec` / `target=landing` means |
|
|
10
|
+
|-------------|-----------------|----------------------------------------|
|
|
11
|
+
| `ssh` (default) | ACCESS host | ACCESS OS |
|
|
12
|
+
| `paradi-gateway` | TARGET host | TARGET OS (not the gateway) |
|
|
13
|
+
|
|
14
|
+
User config is `ACCESS_*` / `TARGET_*` only. Async copy root: `ACCESS_COPY_JOB_ROOT`.
|
|
15
|
+
|
|
16
|
+
## Architecture (read this first)
|
|
17
|
+
|
|
18
|
+
**There is no direct connection from the user's laptop to pods or cluster nodes.**
|
|
19
|
+
|
|
20
|
+
### Shape A — ACCESS_KIND=ssh
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────┐ Hop 1 (automatic) ┌──────────┐ Hop 2 (tools) ┌────────────┐
|
|
24
|
+
│ MCP Client │ ─── SSH (ssh2) ──────► │ ACCESS │ ── kubectl exec ────► │ Pod │
|
|
25
|
+
│ + MCP Server│ │ landing │ ── ssh/sshpass ─────► │ Host/node │
|
|
26
|
+
└─────────────┘ │ │ ── shell / docker ──► │ ACCESS OS │
|
|
27
|
+
└──────────┘ └────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`TARGET_*` (optional) = **default derive selector only** — not a second Context at init. `ssh_exec` = derive + shell.
|
|
31
|
+
|
|
32
|
+
### Shape B — ACCESS_KIND=paradi-gateway
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
┌─────────────┐ Hop 1 ┌──────────┐
|
|
36
|
+
│ MCP Client │ ── SSH (encoded user) ►│ Gateway │──► landing Context = TARGET
|
|
37
|
+
└─────────────┘ ACCESS_USER/TARGET_HOST/TARGET_USER
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Runtime tools run on **TARGET**. Disabled: `context_derive`, `remote_*` / `remote_copy_*` with `target=context`. Use `shell_exec` / `target=landing` / `docker_*` (omit contextKey). SFTP is `degraded` — avoid parallel hammering; prefer smaller copies / `remote_copy_start`.
|
|
41
|
+
|
|
42
|
+
| Hop | Who initiates | What |
|
|
43
|
+
|-----|---------------|------|
|
|
44
|
+
| **Hop 1** | MCP server on user's machine | SSH to `ACCESS_HOST` (always; not exposed as a tool) |
|
|
45
|
+
| **Hop 2** | Landing Context | `shell_exec`, `k8s_*`, `ssh_exec` (ssh only), `docker_*`, `remote_*` |
|
|
46
|
+
|
|
47
|
+
**Common misconception:** `ssh_exec` is **NOT** "local laptop SSH to remote host". It means **ACCESS SSHs to the target** (`ACCESS_KIND=ssh` only).
|
|
48
|
+
|
|
49
|
+
## Capability boundary
|
|
50
|
+
|
|
51
|
+
Remote execution primitives only. No project-specific paths or namespace defaults.
|
|
52
|
+
|
|
53
|
+
- `docker_*` auto-probes `capabilities.docker` (unknown ≠ true).
|
|
54
|
+
- `k8s_*` does **not** gate on `capabilities.kubectl` — failures surface as exitCode; use `context_probe` for presence.
|
|
55
|
+
|
|
56
|
+
## Three Runtime paths (ssh)
|
|
57
|
+
|
|
58
|
+
| Path | Tool(s) | Action |
|
|
59
|
+
|------|---------|--------|
|
|
60
|
+
| **Landing shell** | `shell_exec`, `remote_*` target=landing | Run on ACCESS (or TARGET under Paradi) |
|
|
61
|
+
| **Pod** | `k8s_exec`, `k8s_logs`, `remote_*` target=pod | kubectl exec / logs |
|
|
62
|
+
| **Derived host** | `context_derive` → `ssh_exec`, `remote_*` target=context | ssh/sshpass — **ssh kind only** |
|
|
63
|
+
|
|
64
|
+
### Decision matrix
|
|
65
|
+
|
|
66
|
+
| User intent | Tool (ssh) | Tool (paradi) |
|
|
67
|
+
|-------------|------------|---------------|
|
|
68
|
+
| List/find pods | `k8s_list_pods` | same |
|
|
69
|
+
| Run script in container | `k8s_exec` | same |
|
|
70
|
+
| Read file in container | `remote_read_file` target=pod | same |
|
|
71
|
+
| Host OS shell on landing | `shell_exec` | `shell_exec` (TARGET) |
|
|
72
|
+
| Host OS after jump | `context_derive` + `ssh_exec` / target=context | **disabled** — already on TARGET |
|
|
73
|
+
| Docker on landing | `docker_*` | `docker_*` (no host) |
|
|
74
|
+
| Docker on worker | `docker_*` + contextKey | **disabled** under Paradi |
|
|
75
|
+
| Copy short | `remote_copy_file` | same; no `target=context` under Paradi |
|
|
76
|
+
| Copy large | `remote_copy_start` | same; SFTP degraded |
|
|
77
|
+
|
|
78
|
+
## ssh_exec vs k8s_exec vs docker_* vs shell_exec
|
|
79
|
+
|
|
80
|
+
| Tool | Reaches | Auth / notes |
|
|
81
|
+
|------|---------|----------------|
|
|
82
|
+
| `shell_exec` | Landing OS shell | ACCESS_* (Paradi: TARGET via gateway); optional contextKey |
|
|
83
|
+
| `k8s_exec` | Inside pod container | Landing kubeconfig |
|
|
84
|
+
| `ssh_exec` | Host OS (derive + shell) | ACCESS→host / TARGET_*; unavailable under Paradi |
|
|
85
|
+
| `docker_ps` / `docker_exec` / `docker_logs` | Docker on landing or derived Context | SessionPool; contextKey; auto-probes docker; unknown ≠ true |
|
|
86
|
+
| `context_probe` | Same Context | Returns docker/kubectl tri-state |
|
|
87
|
+
|
|
88
|
+
**Derived-host defaults (`TARGET_USER` / `TARGET_PASSWORD`):** optional under ssh. When both set, omit `user`/`password`. Password-only: omit password, still pass user. See one-line status in server instructions. Under Paradi, derive guidance is replaced by a “landing=TARGET / derive unsupported” note.
|
|
89
|
+
|
|
90
|
+
**Docker vs k8s:** container on host Docker engine → `docker_*`; container in Kubernetes → `k8s_exec` / `k8s_logs`.
|
|
91
|
+
|
|
92
|
+
## Handshake behavior
|
|
93
|
+
|
|
94
|
+
1. Connectivity unknown / first remote ops: `access_check` (ACCESS SSH probe ≤10s; ephemeral; not kubectl)
|
|
95
|
+
2. Kubernetes ops only: then `shell_exec({"command":"kubectl cluster-info --request-timeout=5s"})`
|
|
96
|
+
|
|
97
|
+
If `access_check.reachable=false`: stop; read JSON `message`/`hints` (VPN / `ACCESS_HOST`). If other tools return `code=ACCESS_UNAVAILABLE`: stop; Chinese hints are usually in `stderr`.
|
|
98
|
+
|
|
99
|
+
Always obtain from user or context: namespace, container name for multi-container pods. For host SSH (ssh kind): only ask user/password when `TARGET_*` are unset.
|
|
100
|
+
|
|
101
|
+
## Tool return format
|
|
102
|
+
|
|
103
|
+
On **success** (`exitCode===0`), remote command output (stdout and stderr combined) is in `stdout`; the `stderr` field is empty.
|
|
104
|
+
|
|
105
|
+
On **command failure** (`exitCode≠0`, `success=false`, no thrown exception): merged remote output is still in **`stdout`**. Only tool/connection exceptions (e.g. `ACCESS_UNAVAILABLE`) put the message in `stderr`.
|
|
106
|
+
|
|
107
|
+
Always check both `success` / `exitCode` and `stdout`.
|
|
108
|
+
|
|
109
|
+
## Shell / I/O pitfalls
|
|
110
|
+
|
|
111
|
+
| Topic | Guidance |
|
|
112
|
+
|-------|----------|
|
|
113
|
+
| Shell metacharacters | Every remote command is parsed by bash. Quote `&`, `\|`, `>`, `$`, and URLs. Prefer `remote_copy_file` of an LF script, then `bash script.sh`. |
|
|
114
|
+
| Non-TTY docker | `docker login` / `pull` often exit 0 with empty stdout — use `exitCode` and follow-up probe. |
|
|
115
|
+
| CRLF scripts | Ship LF, or `sed -i 's/\r$//' file` / `tr -d '\r'`. |
|
|
116
|
+
| DOCKER_CONFIG | If permission denied: `export DOCKER_CONFIG=$HOME/dockertmp && mkdir -p "$DOCKER_CONFIG"`. |
|
|
117
|
+
| Registry SOP | Host with egress; `docker login` once; quoted Registry API probe. Never bare `&` in URLs. |
|
|
118
|
+
|
|
119
|
+
## MCP prompts
|
|
120
|
+
|
|
121
|
+
| Prompt | Purpose |
|
|
122
|
+
|--------|---------|
|
|
123
|
+
| `tool-selection` | Pick the right tool for a scenario |
|
|
124
|
+
| `remote-debug-workflow` | list → read/tail → ps workflow |
|
|
125
|
+
|
|
126
|
+
## Error handling
|
|
127
|
+
|
|
128
|
+
| Situation | Action |
|
|
129
|
+
|-----------|--------|
|
|
130
|
+
| `truncatedByLines: true` | Increase `maxLines`, use `offset`, or use `remote_copy_file` |
|
|
131
|
+
| `truncatedByChars: true` | Raise `MAX_OUTPUT_CHARS` or use `remote_copy_file` |
|
|
132
|
+
| `ssh_exec` under Paradi | Expected error — use `shell_exec` / `target=landing` |
|
|
133
|
+
| `ssh_exec` Permission denied | Add password or use `k8s_exec` for pods |
|
|
134
|
+
| Command `success=false` / exit≠0 | Read **stdout** |
|
|
135
|
+
| Empty stdout + exit 0 (docker) | Normal for non-TTY |
|
|
136
|
+
| `k8s_logs` empty | Use `remote_read_file` mode=tail for file logs |
|
|
137
|
+
| Large copy times out | Raise `totalTimeoutMs`; Paradi: expect SFTP flakiness |
|
|
138
|
+
|
|
139
|
+
## Large file copy (local ↔ landing)
|
|
140
|
+
|
|
141
|
+
For multi-GB landing↔local (or pod→local last hop):
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"source": { "target": "landing", "path": "/tmp/big.pt" },
|
|
146
|
+
"dest": { "target": "local", "path": "C:/data/big.pt" },
|
|
147
|
+
"skipIfComplete": true,
|
|
148
|
+
"totalTimeoutMs": 3600000,
|
|
149
|
+
"verify": "size"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- Progress: throttled (`progressIntervalMs`, default 5s); MCP `notifications/progress` if client sends `progressToken`
|
|
154
|
+
- `skipIfComplete`: skip gate follows `verify`
|
|
155
|
+
- Result includes `transferId`; async jobs expose `remote_copy_status`
|
|
156
|
+
|
|
157
|
+
| Layer | Responsibility |
|
|
158
|
+
|-------|----------------|
|
|
159
|
+
| **host-execution-mcp** | Access-mediated remote exec |
|
|
160
|
+
| **User Skill** | Namespace, paths, scripts |
|
|
161
|
+
| **User** | Approving destructive ops |
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
## Access shapes (user mental model)
|
|
4
|
+
|
|
5
|
+
| Shape | Env | Meaning |
|
|
6
|
+
|-------|-----|---------|
|
|
7
|
+
| Single host | `ACCESS_*` only | Land on ACCESS; shell / kubectl / docker there |
|
|
8
|
+
| Bastion → target | `ACCESS_*` + `TARGET_*` | Land on ACCESS; `TARGET_*` is **default derive selector** (not a second Context at init) |
|
|
9
|
+
| Paradi gateway | `ACCESS_KIND=paradi-gateway` + `TARGET_HOST` + `TARGET_USER` | SSH to gateway with encoded identity; landing Context = TARGET |
|
|
10
|
+
|
|
11
|
+
Runtime (shell / docker / kubectl) is **not** configured in mcp.json — Agent chooses from Context capabilities.
|
|
12
|
+
|
|
13
|
+
## Required environment variables
|
|
14
|
+
|
|
15
|
+
| Variable | Description |
|
|
16
|
+
|----------|-------------|
|
|
17
|
+
| `ACCESS_HOST` | First SSH endpoint (jump host, direct target, or Paradi gateway) |
|
|
18
|
+
| `ACCESS_USER` | SSH username for ACCESS |
|
|
19
|
+
|
|
20
|
+
## Optional environment variables
|
|
21
|
+
|
|
22
|
+
| Variable | Default | Description |
|
|
23
|
+
|----------|---------|-------------|
|
|
24
|
+
| `ACCESS_KIND` | `ssh` | `ssh` \| `paradi-gateway` |
|
|
25
|
+
| `ACCESS_PORT` | `22` | SSH port |
|
|
26
|
+
| `ACCESS_PRIVATE_KEY_PATH` | — | Explicit private key path |
|
|
27
|
+
| `ACCESS_PASSWORD` | — | Password auth (dev only); also used to build Paradi compound password when that mode wins |
|
|
28
|
+
| `ACCESS_PARADI_AUTH` | `auto` | Paradi only: `auto` \| `slash` \| `compound`. Default `auto` tries slash then compound on AUTH_FAILED and **pins** the winner for this MCP process |
|
|
29
|
+
| `TARGET_HOST` | — | Default derive host (`ssh`) or Paradi landing host |
|
|
30
|
+
| `TARGET_USER` | — | **Required if `TARGET_HOST` set** (no fallback to `ACCESS_USER`) |
|
|
31
|
+
| `TARGET_PASSWORD` | — | Default derive password (sshpass); ignored for Paradi auth |
|
|
32
|
+
| `DEFAULT_TIMEOUT_MS` | `60000` | Default command timeout |
|
|
33
|
+
| `MAX_OUTPUT_CHARS` | `65536` | Max tool output size |
|
|
34
|
+
|
|
35
|
+
## Policy (not Access)
|
|
36
|
+
|
|
37
|
+
| Variable | Description |
|
|
38
|
+
|----------|-------------|
|
|
39
|
+
| `ACCESS_POLICY_ALLOWED_NAMESPACES` | Comma-separated kubectl namespace whitelist |
|
|
40
|
+
| `ALLOWED_NAMESPACES` | Same (kept as short alias for policy only) |
|
|
41
|
+
|
|
42
|
+
## Ops (not Access matrix)
|
|
43
|
+
|
|
44
|
+
| Variable | Description |
|
|
45
|
+
|----------|-------------|
|
|
46
|
+
| `ACCESS_COPY_JOB_ROOT` | Async copy job state root (default `~/.agent-compose/remote-copy`) |
|
|
47
|
+
|
|
48
|
+
## Removed in 4.0.0
|
|
49
|
+
|
|
50
|
+
Do **not** set: `BASTION_HOST`, `BASTION_USER`, `BASTION_PORT`, `BASTION_PASSWORD`, `BASTION_PRIVATE_KEY_PATH`, `REMOTE_SSH_USER`, `REMOTE_SSH_PASSWORD`.
|
|
51
|
+
If present, `loadConfig` throws with rename hints (`BASTION_HOST` → `ACCESS_HOST`, etc.).
|
|
52
|
+
|
|
53
|
+
## SSH authentication (ACCESS)
|
|
54
|
+
|
|
55
|
+
Configure **one or more** methods; the server passes all of them to the SSH client, which tries each until one succeeds:
|
|
56
|
+
|
|
57
|
+
| Method | When used |
|
|
58
|
+
|--------|-----------|
|
|
59
|
+
| `ACCESS_PRIVATE_KEY_PATH` | File exists at the given path |
|
|
60
|
+
| Default `~/.ssh/id_ed25519`, `id_rsa`, `id_ecdsa` | Used when no explicit key path is set and a default key file exists |
|
|
61
|
+
| SSH agent (`SSH_AUTH_SOCK`) | Socket path is set and the socket file exists |
|
|
62
|
+
| `ACCESS_PASSWORD` | Variable is set |
|
|
63
|
+
|
|
64
|
+
**Examples:**
|
|
65
|
+
|
|
66
|
+
- Key or agent only (passwordless): configure keys/agent; do not set `ACCESS_PASSWORD`.
|
|
67
|
+
- Password only: set `ACCESS_PASSWORD`; no keys required.
|
|
68
|
+
- Agent socket present but empty (common in IDE-launched MCP): also set `ACCESS_PASSWORD`.
|
|
69
|
+
|
|
70
|
+
## MCP client setup
|
|
71
|
+
|
|
72
|
+
The server uses **stdio transport**. See [clients/](clients/).
|
|
73
|
+
|
|
74
|
+
### Minimal
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"host-execution": {
|
|
80
|
+
"command": "npx",
|
|
81
|
+
"args": ["-y", "host-execution-mcp@latest"],
|
|
82
|
+
"env": {
|
|
83
|
+
"ACCESS_HOST": "10.0.0.1",
|
|
84
|
+
"ACCESS_USER": "admin"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### With default derive target
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"ACCESS_KIND": "ssh",
|
|
96
|
+
"ACCESS_HOST": "10.121.120.7",
|
|
97
|
+
"ACCESS_USER": "mccxadmin",
|
|
98
|
+
"ACCESS_PASSWORD": "<secret>",
|
|
99
|
+
"TARGET_HOST": "10.121.35.68",
|
|
100
|
+
"TARGET_USER": "mccuseradmin",
|
|
101
|
+
"TARGET_PASSWORD": "<secret>"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### With Paradi gateway
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"ACCESS_KIND": "paradi-gateway",
|
|
110
|
+
"ACCESS_HOST": "10.121.61.3",
|
|
111
|
+
"ACCESS_USER": "bastion-login",
|
|
112
|
+
"ACCESS_PASSWORD": "<secret>",
|
|
113
|
+
"TARGET_HOST": "10.121.35.68",
|
|
114
|
+
"TARGET_USER": "mccuseradmin"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Customers only set gateway + target. Do **not** put a three-part SSH username or `@##@` password in mcp.json — the server encodes them.
|
|
119
|
+
|
|
120
|
+
Default `ACCESS_PARADI_AUTH=auto` (may omit):
|
|
121
|
+
|
|
122
|
+
1. Try **slash** username `ACCESS_USER/TARGET_HOST/TARGET_USER` + `ACCESS_PASSWORD`
|
|
123
|
+
2. On AUTH_FAILED only, try **compound** username `ACCESS_USER` + `ACCESS_PASSWORD@##@TARGET_HOST:SSH:22:TARGET_USER`
|
|
124
|
+
3. Pin the winner for this MCP process (restart MCP to clear / re-probe)
|
|
125
|
+
|
|
126
|
+
Ops override: set `ACCESS_PARADI_AUTH=slash` or `compound` to force one encoding. `access_check` may report `paradiAuth` after success.
|
|
127
|
+
|
|
128
|
+
Landing Context is already TARGET: use `shell_exec` / `docker_*` (omit contextKey).
|
|
129
|
+
|
|
130
|
+
See [PARADI_ACCESS_PLAN.md](PARADI_ACCESS_PLAN.md) for the full Access / Runtime model.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copy specialized test checklist
|
|
2
|
+
|
|
3
|
+
Hop1 3.0.0 全面端到端清单已迁移至:
|
|
4
|
+
|
|
5
|
+
- **[`docs/HOP1_E2E_CHECKLIST.md`](HOP1_E2E_CHECKLIST.md)** — A1–J4(43 项,最近 **43/43 pass**)
|
|
6
|
+
- **[`docs/HOP1_E2E_RESULTS.json`](HOP1_E2E_RESULTS.json)** — 原始结果
|
|
7
|
+
- Runner:`scripts/hop1-e2e-one.mjs` / `scripts/hop1-e2e-all.mjs`
|
|
8
|
+
|
|
9
|
+
旧 T1–T8(`scripts/specialized-one.mjs`)仍可用,映射见 HOP1_E2E_CHECKLIST 文末。
|