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.
Files changed (126) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/SECURITY.md +72 -0
  5. package/dist/access/docker-runtime.d.ts +38 -0
  6. package/dist/access/docker-runtime.js +61 -0
  7. package/dist/access/docker-runtime.js.map +1 -0
  8. package/dist/access/index.d.ts +110 -0
  9. package/dist/access/index.js +174 -0
  10. package/dist/access/index.js.map +1 -0
  11. package/dist/access/paradi-auth.d.ts +33 -0
  12. package/dist/access/paradi-auth.js +89 -0
  13. package/dist/access/paradi-auth.js.map +1 -0
  14. package/dist/access/paradi-transport.d.ts +10 -0
  15. package/dist/access/paradi-transport.js +39 -0
  16. package/dist/access/paradi-transport.js.map +1 -0
  17. package/dist/access/paradi-username.d.ts +5 -0
  18. package/dist/access/paradi-username.js +14 -0
  19. package/dist/access/paradi-username.js.map +1 -0
  20. package/dist/access/probe.d.ts +14 -0
  21. package/dist/access/probe.js +63 -0
  22. package/dist/access/probe.js.map +1 -0
  23. package/dist/access/session-pool.d.ts +25 -0
  24. package/dist/access/session-pool.js +46 -0
  25. package/dist/access/session-pool.js.map +1 -0
  26. package/dist/access/ssh-transport.d.ts +16 -0
  27. package/dist/access/ssh-transport.js +74 -0
  28. package/dist/access/ssh-transport.js.map +1 -0
  29. package/dist/access/types.d.ts +43 -0
  30. package/dist/access/types.js +21 -0
  31. package/dist/access/types.js.map +1 -0
  32. package/dist/config.d.ts +52 -0
  33. package/dist/config.js +179 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +19 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/instructions.d.ts +4 -0
  39. package/dist/instructions.js +93 -0
  40. package/dist/instructions.js.map +1 -0
  41. package/dist/kubectl/runner.d.ts +32 -0
  42. package/dist/kubectl/runner.js +79 -0
  43. package/dist/kubectl/runner.js.map +1 -0
  44. package/dist/server.d.ts +3 -0
  45. package/dist/server.js +24 -0
  46. package/dist/server.js.map +1 -0
  47. package/dist/ssh/access-client.d.ts +62 -0
  48. package/dist/ssh/access-client.js +470 -0
  49. package/dist/ssh/access-client.js.map +1 -0
  50. package/dist/ssh/access-errors.d.ts +53 -0
  51. package/dist/ssh/access-errors.js +180 -0
  52. package/dist/ssh/access-errors.js.map +1 -0
  53. package/dist/ssh/access-sftp.d.ts +14 -0
  54. package/dist/ssh/access-sftp.js +456 -0
  55. package/dist/ssh/access-sftp.js.map +1 -0
  56. package/dist/ssh/bastion-client.d.ts +60 -0
  57. package/dist/ssh/bastion-client.js +423 -0
  58. package/dist/ssh/bastion-client.js.map +1 -0
  59. package/dist/ssh/bastion-errors.d.ts +62 -0
  60. package/dist/ssh/bastion-errors.js +190 -0
  61. package/dist/ssh/bastion-errors.js.map +1 -0
  62. package/dist/ssh/hop1-sftp.d.ts +14 -0
  63. package/dist/ssh/hop1-sftp.js +437 -0
  64. package/dist/ssh/hop1-sftp.js.map +1 -0
  65. package/dist/tools/copy-job-schemas.d.ts +709 -0
  66. package/dist/tools/copy-job-schemas.js +48 -0
  67. package/dist/tools/copy-job-schemas.js.map +1 -0
  68. package/dist/tools/register.d.ts +14 -0
  69. package/dist/tools/register.js +619 -0
  70. package/dist/tools/register.js.map +1 -0
  71. package/dist/tools/remote-schemas.d.ts +879 -0
  72. package/dist/tools/remote-schemas.js +214 -0
  73. package/dist/tools/remote-schemas.js.map +1 -0
  74. package/dist/utils/copy-executor.d.ts +41 -0
  75. package/dist/utils/copy-executor.js +231 -0
  76. package/dist/utils/copy-executor.js.map +1 -0
  77. package/dist/utils/copy-job-manager.d.ts +34 -0
  78. package/dist/utils/copy-job-manager.js +400 -0
  79. package/dist/utils/copy-job-manager.js.map +1 -0
  80. package/dist/utils/copy-job-store.d.ts +18 -0
  81. package/dist/utils/copy-job-store.js +104 -0
  82. package/dist/utils/copy-job-store.js.map +1 -0
  83. package/dist/utils/copy-job-types.d.ts +133 -0
  84. package/dist/utils/copy-job-types.js +37 -0
  85. package/dist/utils/copy-job-types.js.map +1 -0
  86. package/dist/utils/copy-transfer.d.ts +59 -0
  87. package/dist/utils/copy-transfer.js +86 -0
  88. package/dist/utils/copy-transfer.js.map +1 -0
  89. package/dist/utils/remote-copy.d.ts +14 -0
  90. package/dist/utils/remote-copy.js +115 -0
  91. package/dist/utils/remote-copy.js.map +1 -0
  92. package/dist/utils/remote-executor.d.ts +15 -0
  93. package/dist/utils/remote-executor.js +34 -0
  94. package/dist/utils/remote-executor.js.map +1 -0
  95. package/dist/utils/remote-target.d.ts +22 -0
  96. package/dist/utils/remote-target.js +75 -0
  97. package/dist/utils/remote-target.js.map +1 -0
  98. package/dist/utils/result.d.ts +87 -0
  99. package/dist/utils/result.js +81 -0
  100. package/dist/utils/result.js.map +1 -0
  101. package/dist/utils/shell-quote.d.ts +22 -0
  102. package/dist/utils/shell-quote.js +55 -0
  103. package/dist/utils/shell-quote.js.map +1 -0
  104. package/dist/utils/timeout.d.ts +1 -0
  105. package/dist/utils/timeout.js +18 -0
  106. package/dist/utils/timeout.js.map +1 -0
  107. package/dist/utils/truncate.d.ts +5 -0
  108. package/dist/utils/truncate.js +12 -0
  109. package/dist/utils/truncate.js.map +1 -0
  110. package/dist/version.d.ts +1 -0
  111. package/dist/version.js +7 -0
  112. package/dist/version.js.map +1 -0
  113. package/docs/AGENT_GUIDE.md +161 -0
  114. package/docs/CONFIGURATION.md +130 -0
  115. package/docs/COPY_SPECIALIZED_TEST_CHECKLIST.md +9 -0
  116. package/docs/HOP1_E2E_CHECKLIST.md +128 -0
  117. package/docs/HOP1_E2E_RESULTS.json +647 -0
  118. package/docs/PARADI_ACCESS_PLAN.md +272 -0
  119. package/docs/REMOTE_COPY_JOB_API.md +92 -0
  120. package/docs/TODO.md +66 -0
  121. package/docs/USER_GUIDE.zh-CN.md +93 -0
  122. package/docs/clients/claude-desktop.json.example +12 -0
  123. package/docs/clients/cursor-npx.json.example +12 -0
  124. package/docs/clients/cursor.json.example +12 -0
  125. package/docs/clients/vscode.json.example +12 -0
  126. package/package.json +65 -0
@@ -0,0 +1,272 @@
1
+ # Plan: Access / Runtime 分层 + Paradi 合入
2
+
3
+ Status: **accepted · Phase 0 frozen** · Updated: 2026-07-22
4
+
5
+ ## 终态目标
6
+
7
+ 干净的远程执行 MCP:**用户只配置「如何获得 HostExecutionContext」**;shell / docker / kubectl 是 Runtime,由 Agent 按落点 `capabilities` 选择,**不进 mcp.json**。
8
+
9
+ ```text
10
+ AccessTransport.createContext()
11
+ │
12
+ ▼
13
+ HostExecutionContext(描述 + capabilities;无 session)
14
+ │
15
+ │ deriveContext(parent, host…) ← 仅 ssh 第二跳,按需创建
16
+ ▼
17
+ Runtime(context) → shell / docker_* / k8s_* / copy
18
+ │
19
+ └──► SessionPool.get(context.key) ← 旁路:按 key 借还 SSH;不进 Context
20
+ ```
21
+
22
+ ### 三条硬标准
23
+
24
+ 1. **无 Runtime 配置污染** — 不设 `ENABLE_DOCKER`。
25
+ 2. **无旧双轨** — Phase 4 删光死代码与旧 env 名。
26
+ 3. **文档 = 代码**。
27
+
28
+ ---
29
+
30
+ ## Phase 1 前冻结的不变量(守住不推倒)
31
+
32
+ | # | 不变量 | 反例(禁止) |
33
+ |---|--------|--------------|
34
+ | A | **Context 永远无 session** | `Context { sshClient }`;MCP request 间串会话 |
35
+ | B | **Runtime 永远不创建 Access/SSH** | `docker_*(host=)` 内部再拼 ssh;Runtime 自己 `new Client()` |
36
+ | C | **`TARGET_*` = 默认 derive 选择器,不是启动时自动建第二 Context** | init 预建 Context_TARGET;或「配了 TARGET 就默认落点已在 T」 |
37
+ | D | **capabilities 诚实:登录成功 ≠ 有 docker/kubectl** | SSH ok 就写 `docker:true`;Agent 当 infallible |
38
+
39
+ 另:现场「三跳」= 口语(ssh→ssh→docker)。模型 = Access 链 + Runtime;勿写「三跳作废」。
40
+
41
+ ### 不变量 D — capabilities 生命周期
42
+
43
+ ```ts
44
+ // createContext / 首次连通后(仅能确定的)
45
+ capabilities: {
46
+ exec: true, // 或 false:能跑 shell
47
+ docker: "unknown",
48
+ kubectl: "unknown",
49
+ // 可选 source: "declared" | "probed"
50
+ }
51
+ ```
52
+
53
+ - **禁止**用「SSH 成功」推断 Runtime 能力。
54
+ - **probe**(`which docker` / `kubectl version --client` 等)后改为 `true` | `false`;可懒探测(首次 docker_* / k8s_* 前)或显式 probe 工具。
55
+ - Agent:`unknown` → 先 probe 或试一次并处理失败;**不得**把 unknown 当 true。
56
+ - Phase 1 可只保证 `exec`;docker/kubectl 字段先占位 `unknown`,Phase 2+ 接 probe。
57
+
58
+ ### 命名冻结 — `TARGET_PASSWORD`
59
+
60
+ 保持 **`TARGET_PASSWORD`**,不用 `TARGET_SSH_PASSWORD`。
61
+ Access 已表明走何种 Transport;密码是「目标侧凭证」,未来可复用(ssh / paradi fallback / 其它),勿把协议钉进 env 名。
62
+
63
+ ---
64
+
65
+ ## 核心不变量(完整)
66
+
67
+ 1. AccessTransport **生产** Context;Runtime **只消费** Context。
68
+ 2. docker / kubectl = Runtime,不是 Access 第二跳。
69
+ 3. Context 无 session;**SessionPool** 按稳定 `context.key` 管连接(与 Context 分离)。
70
+ 4. `ssh_exec` ≡ `deriveContext` + shell(仅 `ACCESS_KIND=ssh`)。
71
+ 5. 帕拉迪 = `ParadiTransport.createContext()` → **普通** `HostExecutionContext`(无 ParadiContext 子类;Runtime 不知网关)。
72
+ 6. Policy ≠ Access。
73
+ 7. 用户形态按「几个 IP / 是否网关」分,不按「机器叫不叫跳板」分。
74
+
75
+ ---
76
+
77
+ ## 用户怎么配(真正三种)
78
+
79
+ 只认:`ACCESS_*` = 本机第一次连谁;`TARGET_*` = 默认第二跳选择器(按需 derive,非自动双 Context)。
80
+ **不要** `BASTION_*` / `REMOTE_*` / `PARADI_*` 分套变量。
81
+
82
+ | # | 形态 | IP | KIND | TARGET_* | 启动时 Context |
83
+ |---|------|-----|------|----------|----------------|
84
+ | 1 | **单机** | 1 | `ssh`(可省略) | **不要** | 仅 ACCESS |
85
+ | 2 | **跳板→目标** | 2 | `ssh` | **要**(HOST+USER) | **仍仅 ACCESS**;TARGET 供默认 derive |
86
+ | 3 | **帕拉迪** | 2 | `paradi-gateway` | **要** | 一次 create → 落点已是 T |
87
+
88
+ > **为何不拆 Direct / 仅跳板?** 用户都是「ssh 上一台」;机器职能不是 Access 种类。旧 `ssh-direct` / 无 TARGET 的 bastion KIND → **形态 1**。
89
+
90
+ ### 形态 1 — 单机
91
+
92
+ ```json
93
+ {
94
+ "ACCESS_KIND": "ssh",
95
+ "ACCESS_HOST": "10.121.120.7",
96
+ "ACCESS_USER": "mccxadmin",
97
+ "ACCESS_PASSWORD": "<secret>"
98
+ }
99
+ ```
100
+
101
+ ```text
102
+ Client ──SSH──► Context0(ACCESS) ── Runtime
103
+ ```
104
+
105
+ 调用时仍可 `ssh_exec(另一台)` → derive;不必在 mcp.json 写 TARGET。
106
+
107
+ ### 形态 2 — 跳板 → 目标
108
+
109
+ ```json
110
+ {
111
+ "ACCESS_KIND": "ssh",
112
+ "ACCESS_HOST": "10.121.120.7",
113
+ "ACCESS_USER": "mccxadmin",
114
+ "ACCESS_PASSWORD": "<secret>",
115
+ "TARGET_HOST": "10.121.35.68",
116
+ "TARGET_USER": "mccuseradmin",
117
+ "TARGET_PASSWORD": "<secret>"
118
+ }
119
+ ```
120
+
121
+ ```text
122
+ 启动: Client ──SSH──► Context0(ACCESS) ← 只有这一个 Context
123
+ 按需: Context0 ──derive──► Context1(TARGET) ── Runtime
124
+ (ssh_exec 省略 host 时可用默认 TARGET_*;也可覆盖到别的 host)
125
+ ```
126
+
127
+ `TARGET_PASSWORD`:目标侧凭证(需要时);不是 docker/registry 密码;**勿改名** `TARGET_SSH_PASSWORD`。
128
+ **禁止** init 时预建 Context1;多 TARGET 未来 = 多次 derive / 选择器,不是启动 N 个 Context。
129
+
130
+ ### 形态 3 — 帕拉迪
131
+
132
+ ```json
133
+ {
134
+ "ACCESS_KIND": "paradi-gateway",
135
+ "ACCESS_HOST": "10.121.61.3",
136
+ "ACCESS_USER": "zhangyimingzgc",
137
+ "ACCESS_PASSWORD": "<secret>",
138
+ "TARGET_HOST": "10.121.35.68",
139
+ "TARGET_USER": "mccuseradmin"
140
+ }
141
+ ```
142
+
143
+ ```text
144
+ ParadiTransport.createContext() → Context0(endpoint=T, transport=paradi-gateway)
145
+ Client ──SSH(身份编码目标)──► T ── Runtime (Runtime 无帕拉迪分支)
146
+ ```
147
+
148
+ 实现细节(用户不配):`sshUsername = ACCESS_USER/TARGET_HOST/TARGET_USER`。一般不配 `TARGET_PASSWORD`。
149
+
150
+ ---
151
+
152
+ ## Policy(与 Access 拆开)
153
+
154
+ Access **不含** `ALLOWED_NAMESPACES`。另层例如 `ACCESS_POLICY`。Phase 1 可暂缓。
155
+
156
+ ---
157
+
158
+ ## loadConfig 校验
159
+
160
+ | KIND | 规则 |
161
+ |------|------|
162
+ | `ssh`(默认) | ACCESS 必填;TARGET 可选;**若存在 `TARGET_HOST` → `TARGET_USER` 必填**(禁止 fallback 到 `ACCESS_USER`) |
163
+ | `paradi-gateway` | ACCESS + TARGET_HOST + TARGET_USER 必填;忽略 TARGET_PASSWORD 默认认证 |
164
+
165
+ `ACCESS_KIND` 仅:`ssh` | `paradi-gateway`。
166
+
167
+ 配置路径:`env → AccessTransport.createContext → Runtime(+ SessionPool)`;禁止 tool 直读 env。
168
+
169
+ ---
170
+
171
+ ## 形态 → Context → Runtime
172
+
173
+ | 形态 | 启动 Context | derive | Runtime |
174
+ |------|--------------|--------|---------|
175
+ | 1 单机 | ACCESS | 调用时可选 | Agent × capabilities |
176
+ | 2 跳板→目标 | **仅 ACCESS**;TARGET = 默认 derive 信息 | 按需 | 同上 |
177
+ | 3 帕拉迪 | T(一次 create) | 无「再 SSH」derive | 同上;capabilities 诚实 |
178
+
179
+ 口语「三跳」≈ 形态 2 + docker Runtime(SSH×2 + docker),不是第三种 Access。
180
+
181
+ ---
182
+
183
+ ## HostExecutionContext
184
+
185
+ ```ts
186
+ type Cap = true | false | "unknown"
187
+
188
+ interface ContextCapabilities {
189
+ exec: Cap
190
+ docker: Cap
191
+ kubectl: Cap
192
+ }
193
+
194
+ interface HostExecutionContext {
195
+ key: string // 稳定;禁止每次 random uuid
196
+ transport: AccessTransportKind // ssh | paradi-gateway
197
+ endpoint: { host: string; user?: string }
198
+ parentKey?: string // 仅 derive 出的子 Context
199
+ capabilities: ContextCapabilities // 初值见不变量 D;probe 后更新
200
+ // 禁止: sshClient / session / stream
201
+ }
202
+ ```
203
+
204
+ ```ts
205
+ SessionPool.get(ctx.key) → SSH session // Runtime/Transport 调用;生命周期归 Pool
206
+
207
+ // ACCESS_KIND=ssh
208
+ ssh_exec(host?, command) ≡
209
+ deriveContext(accessContext, host ?? defaultTargetFromConfig)
210
+ shellExec(derived, command)
211
+ ```
212
+
213
+ ---
214
+
215
+ ## 实施路线
216
+
217
+ ### Phase 0 — 文档
218
+
219
+ - [x] 三种用户形态;KIND=`ssh`|`paradi-gateway`;Runtime 不进配置
220
+ - [x] 冻结不变量 A/B/C/D;`TARGET_PASSWORD` 命名;SessionPool 旁路;TARGET=默认 derive
221
+ - [x] CONFIGURATION / AGENT_GUIDE / USER_GUIDE / clients → ACCESS_*(与 Phase 1 同 PR)
222
+
223
+ ### Phase 1 — Context + `ssh`
224
+
225
+ - [x] `HostExecutionContext`(无 session)+ `SessionPool`(按 key)
226
+ - [x] `SshTransport.createContext` → 仅 Context0(ACCESS);capabilities 初值 `exec` + docker/kubectl=`unknown`
227
+ - [x] loadConfig + TARGET 作 default derive;有 TARGET_HOST 则 TARGET_USER 必填;字段名 `TARGET_PASSWORD`
228
+ - [x] 入口 shell;**ssh_exec = derive+shell**;k8s_* 仍经 ACCESS(bastion)执行
229
+ - [x] CONFIGURATION / AGENT_GUIDE / USER_GUIDE / clients → ACCESS_*(legacy BASTION_* 薄别名)
230
+ - [x] 版本 3.1.0
231
+
232
+ **不做:** docker 产品面、Paradi、Policy 大改、工具大改名。
233
+
234
+ ### Phase 2 — docker Runtime + probe
235
+
236
+ - [x] `docker_ps` / `docker_exec` / `docker_logs`(经 SessionPool,不自建 SSH)
237
+ - [x] `context_probe` + lazy probe 更新 `capabilities.docker`;AGENT_GUIDE/instructions:unknown ≠ true
238
+ - [x] 版本 3.2.0
239
+
240
+ ### Phase 3 — `paradi-gateway`
241
+
242
+ - [x] `ParadiTransport.createContext` → 普通 Context(endpoint=T)
243
+ - [x] capabilities 诚实(`sftp: degraded`、`channelOpenSensitive`)
244
+ - [x] loadConfig 接受 paradi;Hop1 身份编码;禁止 derive/`ssh_exec`
245
+ - [x] 版本 3.3.0
246
+
247
+ ### Phase 4 — 清理
248
+
249
+ - [x] 删 `BASTION_*` / `REMOTE_SSH_*` loadConfig 别名(残留则报错提示改名)
250
+ - [x] 文案 / README / CONFIGURATION / SECURITY / examples → ACCESS_* only
251
+ - [x] Policy:`ACCESS_POLICY_ALLOWED_NAMESPACES`(保留 `ALLOWED_NAMESPACES` 短名)
252
+ - [x] 版本 **4.0.0** breaking changelog
253
+
254
+ ---
255
+
256
+ ## 明确不做
257
+
258
+ - Context 持 session;Runtime 创建 SSH;init 预建 TARGET Context
259
+ - SSH 成功 → 写死 `docker`/`kubectl: true`;Agent 把 `unknown` 当 true
260
+ - `TARGET_SSH_PASSWORD`;`TARGET_USER` fallback `ACCESS_USER`
261
+ - `ParadiContext` 子类 / Runtime 内 `if (paradi)`
262
+ - 单机拆 Direct vs 仅跳板;配置写 docker/kubectl
263
+ - expect 菜单;`BASTION_*`/`REMOTE_*`/`PARADI_HOST`;用户 KIND=`ssh-direct`
264
+ - 写「三跳作废」(改用「口语三跳 = Access + Runtime」)
265
+ - 未验证 `@##@` 方式 B
266
+
267
+ ---
268
+
269
+ ## 现网锚点(实现者)
270
+
271
+ - `bastion-client.ts` · `hop1-sftp.ts` · `remote-executor.ts` → Phase1:derive + SessionPool
272
+ - 外围文档仅 ACCESS_* / TARGET_*(4.0.0 已删 BASTION_* 别名)。
@@ -0,0 +1,92 @@
1
+ # Remote Copy Job API (M2)
2
+
3
+ Status: **implemented** (2.2.0+; Hop1 engine **3.0.0**) — tools: `remote_copy_start` / `remote_copy_status` / `remote_copy_cancel`.
4
+
5
+ ## Framing
6
+
7
+ | Phase | Goal |
8
+ |-------|------|
9
+ | **M1 (done)** | Copy **Engine**: Hop1 via `ssh2-sftp-client` (file + directory), progress, `totalTimeoutMs`, `transferId`, `skipIfComplete` |
10
+ | **M2 (done)** | Expose Engine as async Job API — **not** a spawn-and-forget wrapper around blocking `remote_copy_file` |
11
+ | **M3** | Agent policy: auto-async when size **or** ETA exceeds threshold (helpers in `copy-job-types.ts`) — never auto-promote sync tool |
12
+
13
+ ```
14
+ remote_copy_file (sync) ──► same Copy Engine ◄── remote_copy_start (async job)
15
+ │
16
+ ▼
17
+ remote_copy_status / cancel
18
+ ```
19
+
20
+ ## Tools
21
+
22
+ ### `remote_copy_start`
23
+
24
+ Create job → return immediately.
25
+
26
+ - Input: same endpoints + options (`skipIfComplete`, `verify`, `totalTimeoutMs`, …)
27
+ - Output: `transferId`, `status: running`, optional `estimatedSeconds`, `jobStorePath`, `message`
28
+
29
+ ### `remote_copy_status`
30
+
31
+ - Input: `{ transferId }` — only async `remote_copy_start` ids
32
+ - Output: status machine + `progress{ transferredBytes, totalBytes, percent, speedBps, etaSeconds, skippedBytes, currentHopIndex }`
33
+
34
+ ### `remote_copy_cancel`
35
+
36
+ - Input: `{ transferId }`
37
+ - Output: `{ transferId, status, message }` — aborts in-process worker via `AbortSignal`
38
+ - Hop1 SFTP: independent SFTP session ended promptly (exec SSH client is **not** disconnected)
39
+ - Hop2 kubectl/scp: best-effort — abort checked between hops; `exec` closes the SSH channel when signalled
40
+ - Terminal statuses are fenced (cancel cannot overwrite success; success cannot overwrite cancelled)
41
+ - On MCP restart, leftover `queued|running` jobs are reconciled to `failed`
42
+
43
+ ## State machine
44
+
45
+ ```
46
+ queued → running → success
47
+ → failed
48
+ → cancelled
49
+ → timeout
50
+ ```
51
+
52
+ ## Persistence (Scheme A)
53
+
54
+ Do **not** store job state in Cursor conversation.
55
+
56
+ ```
57
+ ~/.agent-compose/remote-copy/jobs/<transferId>.json
58
+ ```
59
+
60
+ Override root with env `BASTION_COPY_JOB_ROOT`.
61
+
62
+ Record shape: `CopyJobRecord` in `src/utils/copy-job-types.ts`.
63
+
64
+ ## Executor abstraction
65
+
66
+ One user-facing API; Engine routes hops:
67
+
68
+ | Hop | Executor kind |
69
+ |-----|---------------|
70
+ | local ↔ bastion | `sftp` (`ssh2-sftp-client`, separate TCP from exec) |
71
+ | pod ↔ bastion | `kubectl` |
72
+ | host ↔ bastion | `scp` |
73
+ | multi-hop | `pipeline` (staging on bastion) |
74
+
75
+ ## Alignment with M1
76
+
77
+ | M1 field | M2 usage |
78
+ |----------|----------|
79
+ | `transferId` (`copy-…`) | Job primary key |
80
+ | `CopyProgress` | Mapped via `engineProgressToJobProgress` |
81
+ | `skipIfComplete` / `skipped` / `skipReason` / `skippedBytes` | Skip gate = verify (size\|size+sha256); none+skip rejected |
82
+ | `totalTimeoutMs` | Wall-clock budget; async terminal status `timeout` |
83
+ | Sync `remote_copy_file` | Short copies; long copies → `remote_copy_start` (never auto-async) |
84
+
85
+ ## Async threshold (policy)
86
+
87
+ `shouldPreferAsyncCopy` in `copy-job-types.ts`:
88
+
89
+ - size > 256 MiB **or**
90
+ - estimatedSeconds > 120
91
+
92
+ Use as agent guidance only; do not auto-switch inside `remote_copy_file`.
package/docs/TODO.md ADDED
@@ -0,0 +1,66 @@
1
+ # TODO — known gaps & follow-ups
2
+
3
+ As of **5.0.0**. Items below are intentional or deferred; do not treat as regressions unless marked otherwise.
4
+
5
+ ## Done in 5.0 cleanup (this branch)
6
+
7
+ - [x] Paradi Zod fail-fast: remote schemas omit `target=context` / `contextKey` under `ACCESS_KIND=paradi-gateway`
8
+ - [x] Copy assert rejects Paradi `host` **and** `context` before materialize
9
+ - [x] Agent-facing tool descriptions: drop Hop1/Hop2 jargon; docs aligned to ACCESS/Context naming
10
+
11
+ ## 5.1 backlog
12
+
13
+ ### 1. Remote endpoint API — remove `target=context` pollution (ssh)
14
+
15
+ ```ts
16
+ // TODO(remote-endpoint-union): Split agent-facing copy/read endpoints:
17
+ // { target: landing|pod|local, path, ... } vs { contextKey, path }
18
+ // Keep materialize internal; stop overloading target=context on the same union.
19
+ ```
20
+
21
+ ### 2. KubectlRunner → SessionPool, then optional `k8s_*(contextKey)`
22
+
23
+ ```ts
24
+ // TODO(kubectl-session-pool): Route kubectl through SessionPool (same as shell/docker).
25
+ // Then optionally expose k8s_*(contextKey?) for derived-host kubeconfig — doc honesty first in 5.0.
26
+ ```
27
+
28
+ ### 3. SessionPool TTL / LRU
29
+
30
+ ```ts
31
+ // TODO(session-pool-eviction): Bound derived Context sessions (TTL + LRU) so long MCP
32
+ // processes do not accumulate idle SSH sessions indefinitely.
33
+ ```
34
+
35
+ ### 4. sshpass password in argv (security debt)
36
+
37
+ ```ts
38
+ // TODO(sshpass-argv): Password appears in ps on landing when using TARGET_PASSWORD / tool password.
39
+ Prefer keys; longer-term: askpass / env-file / ControlMaster without -p in argv.
40
+ See SECURITY.md.
41
+ ```
42
+
43
+ ### 5. Copy cancel on kubectl/scp is best-effort
44
+
45
+ ```ts
46
+ // TODO(copy-cancel-runtime): remote_copy_cancel aborts the in-process worker via AbortSignal.
47
+ // - local↔landing SFTP: independent ssh2-sftp-client session ended promptly; exec SSH kept.
48
+ // - kubectl cp / scp on landing: channel close + abort between hops; in-flight process may continue.
49
+ // Goal ("干净秒杀任意 hop") is NOT met for kubectl/scp yet.
50
+ // Follow-ups: track PIDs / trap-kill wrapper; document partial files on cancel.
51
+ ```
52
+
53
+ ## Related (not blocking)
54
+
55
+ ```ts
56
+ // TODO(m3-agent-policy): Wire shouldPreferAsyncCopy into stronger agent/tool
57
+ // guidance (size>256MiB OR ETA>2min → remote_copy_start). Do NOT auto-promote sync→async.
58
+ ```
59
+
60
+ ```ts
61
+ // TODO(registry-check): Optional structured registry_check tool (login + manifest probe).
62
+ ```
63
+
64
+ ## Access evolution (Paradi)
65
+
66
+ See **[PARADI_ACCESS_PLAN.md](PARADI_ACCESS_PLAN.md)** — Phase1–4 complete (4.0.0); 5.0 renames + Paradi Zod fail-fast.
@@ -0,0 +1,93 @@
1
+ # host-execution-mcp 使用教程
2
+
3
+ **本机 Cursor → SSH(ACCESS / 帕拉迪网关)→ 落点 HostExecutionContext → kubectl / docker / shell**(笔记本不直连 Pod)。
4
+
5
+ 工具名:`access_check` / `shell_exec` / `context_*` / `k8s_*` / `docker_*` / `remote_*`。在 `ACCESS_KIND=ssh` 时落点是 ACCESS;在 `paradi-gateway` 时落点是 **TARGET**(不是网关机)。详见 [AGENT_GUIDE.md](AGENT_GUIDE.md) / [CONFIGURATION.md](CONFIGURATION.md)。
6
+
7
+ ---
8
+
9
+ ## 1. 安装 Node.js
10
+
11
+ 需要 **Node.js >= 18**。官网 [nodejs.org](https://nodejs.org) 下载 LTS,安装后验证:
12
+
13
+ ```bash
14
+ node -v
15
+ ```
16
+
17
+ ---
18
+
19
+ ## 2. MCP 配置(Cursor)
20
+
21
+ 配置文件:`.cursor/mcp.json`,或 Cursor → Settings → MCP → Edit Config。
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "host-execution": {
27
+ "command": "npx",
28
+ "args": ["-y", "host-execution-mcp@latest"],
29
+ "env": {
30
+ "ACCESS_HOST": "10.121.120.7",
31
+ "ACCESS_USER": "your-user"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ `ACCESS_HOST` + `ACCESS_USER` 填你**本机能 SSH 直连的入口机**(跳板或目标机)即可。可选 `TARGET_HOST` / `TARGET_USER` / `TARGET_PASSWORD` 作为默认第二跳(见 CONFIGURATION.md)。
39
+
40
+ 保存后重启 MCP 服务。验证连通:
41
+
42
+ > 先用 host-execution 跑 `access_check`。若要做 Kubernetes 相关操作,通过后再跑 `kubectl cluster-info --request-timeout=5s`(纯 ACCESS/主机 SSH 任务不必跑 cluster-info)。
43
+
44
+ 若提示 `ACCESS_UNAVAILABLE` / 无法连接:先检查本机 VPN 与 `ACCESS_HOST`,不要反复重试其他工具。
45
+
46
+ ---
47
+
48
+ ## 3. 怎么用
49
+
50
+ 对 Agent **用自然语言**说清:**命名空间、负载/Pod 名、文件路径**;需要时加「只读,不要改」。
51
+
52
+ | 你想做的事 | 告诉 Agent |
53
+ |-----------|-----------|
54
+ | 找 Pod | 命名空间 + Deployment/负载名 |
55
+ | 看日志 | Pod 名 + 日志**文件路径**(训练日志多在挂载盘,不是 stdout) |
56
+ | 拷到本地 | 远程路径 + 本地目录(Agent 读取后写入本机) |
57
+ | 排查故障 | 日志 / dmesg / core 路径 + 「只读分析」 |
58
+
59
+ 输出太大被截断时,让 Agent 用 `grep`、`tail` 缩小范围。
60
+
61
+ ---
62
+
63
+ ## 4. 示例话术
64
+
65
+ **查负载**
66
+
67
+ > `his-test` 命名空间,列出 `jd-joyvideo-test` 的 Pod 状态和 IP。
68
+
69
+ **看训练日志**
70
+
71
+ > Pod `jd-joyvideo-test-xxx`,tail `/mnt/code/.../log.65.10.121.33.74` 最后 200 行,看有没有 Killed 或 MUSA error。只读。
72
+
73
+ **拷 log 到本地**
74
+
75
+ > 把 Pod `jd-joyvideo-test-xxx` 上的 `/path/to/log` 和 `/path/to/core.mudmp` 拷到本地 `jd-joyvideo-logs/`,不要改远程文件。
76
+
77
+ **分析训练失败**
78
+
79
+ > 只读分析 Pod 上的 mudmp 和训练 log,启动脚本 `/mnt/code/0407/cluster/dist_run_fsdp8.sh`,说明失败原因。
80
+
81
+ **批量检查**
82
+
83
+ > 对 `his-test` 里所有 Running 的 `jd-joyvideo-test` Pod 跑 `mthreads-gmi -q | grep Bios`,汇总版本。
84
+
85
+ ---
86
+
87
+ ## 常见问题
88
+
89
+ - **MCP 起不来**:检查 `node -v`、堡垒机地址账号、本机能否 `ssh user@10.121.120.7`
90
+ - **k8s_logs 为空**:容器日志流(stdout+stderr)可能为空;日志在文件里时让 Agent 读挂载路径,不要只用 `k8s_logs`
91
+ - **多个节点都 Killed**:先找第一个报 MUSA/SIGABRT 的 rank,其余可能是连锁退出
92
+
93
+ 更多配置见 [CONFIGURATION.md](./CONFIGURATION.md)。
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "host-execution": {
4
+ "command": "node",
5
+ "args": ["<ABS_PATH>/packages/mcp/host-execution/dist/index.js"],
6
+ "env": {
7
+ "ACCESS_HOST": "10.0.0.1",
8
+ "ACCESS_USER": "your-user"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "host-execution": {
4
+ "command": "npx",
5
+ "args": ["-y", "host-execution-mcp@latest"],
6
+ "env": {
7
+ "ACCESS_HOST": "10.0.0.1",
8
+ "ACCESS_USER": "your-user"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "host-execution": {
4
+ "command": "node",
5
+ "args": ["<ABS_PATH>/packages/mcp/host-execution/dist/index.js"],
6
+ "env": {
7
+ "ACCESS_HOST": "10.0.0.1",
8
+ "ACCESS_USER": "your-user"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "host-execution": {
4
+ "command": "node",
5
+ "args": ["<ABS_PATH>/packages/mcp/host-execution/dist/index.js"],
6
+ "env": {
7
+ "ACCESS_HOST": "10.0.0.1",
8
+ "ACCESS_USER": "your-user"
9
+ }
10
+ }
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "host-execution-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Access -> HostExecutionContext -> Runtime MCP for remote shell/docker/kubectl (SSH jump or Paradi gateway)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "host-execution-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE",
14
+ "CHANGELOG.md",
15
+ "SECURITY.md",
16
+ "docs"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "start": "node dist/index.js",
21
+ "dev": "tsc && node dist/index.js",
22
+ "test": "node --import tsx --test tests/*.test.ts",
23
+ "lint": "eslint src tests",
24
+ "format": "prettier --write .",
25
+ "format:check": "prettier --check .",
26
+ "prepublishOnly": "npm run build && npm test"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "host-execution",
34
+ "kubernetes",
35
+ "kubectl",
36
+ "ssh",
37
+ "paradi"
38
+ ],
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/your-org/host-execution-mcp.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/your-org/host-execution-mcp/issues"
46
+ },
47
+ "homepage": "https://github.com/your-org/host-execution-mcp#readme",
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.12.1",
50
+ "ssh2": "^1.16.0",
51
+ "ssh2-sftp-client": "^12.1.1",
52
+ "zod": "^3.24.2"
53
+ },
54
+ "devDependencies": {
55
+ "@eslint/js": "^9.23.0",
56
+ "@types/node": "^22.13.10",
57
+ "@types/ssh2": "^1.15.4",
58
+ "@types/ssh2-sftp-client": "^9.0.6",
59
+ "eslint": "^9.23.0",
60
+ "prettier": "^3.5.3",
61
+ "tsx": "^4.19.3",
62
+ "typescript": "^5.8.2",
63
+ "typescript-eslint": "^8.28.0"
64
+ }
65
+ }