enterprise-ssh-mcp-server 0.3.1 → 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.
Files changed (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +112 -0
  3. package/dist/app/create-app.d.ts +4 -0
  4. package/dist/app/create-app.js +38 -0
  5. package/dist/app/create-app.js.map +1 -0
  6. package/dist/app/tool-context.d.ts +258 -0
  7. package/dist/app/tool-context.js +683 -0
  8. package/dist/app/tool-context.js.map +1 -0
  9. package/dist/env-profiles.d.ts +1 -0
  10. package/dist/env-profiles.js +6 -2
  11. package/dist/env-profiles.js.map +1 -1
  12. package/dist/env-reload.d.ts +36 -0
  13. package/dist/env-reload.js +65 -0
  14. package/dist/env-reload.js.map +1 -0
  15. package/dist/host-ca-trust-config.d.ts +10 -0
  16. package/dist/host-ca-trust-config.js +43 -0
  17. package/dist/host-ca-trust-config.js.map +1 -0
  18. package/dist/index.js +5 -1798
  19. package/dist/index.js.map +1 -1
  20. package/dist/openssh-command.d.ts +34 -0
  21. package/dist/openssh-command.js +217 -0
  22. package/dist/openssh-command.js.map +1 -0
  23. package/dist/openssh-temporary.d.ts +24 -0
  24. package/dist/openssh-temporary.js +82 -0
  25. package/dist/openssh-temporary.js.map +1 -0
  26. package/dist/openssh-trust.d.ts +15 -0
  27. package/dist/openssh-trust.js +91 -0
  28. package/dist/openssh-trust.js.map +1 -0
  29. package/dist/runtime-config.d.ts +1 -0
  30. package/dist/runtime-config.js +10 -1
  31. package/dist/runtime-config.js.map +1 -1
  32. package/dist/ssh-host-key.d.ts +27 -0
  33. package/dist/ssh-host-key.js +235 -0
  34. package/dist/ssh-host-key.js.map +1 -0
  35. package/dist/ssh-session.js +7 -3
  36. package/dist/ssh-session.js.map +1 -1
  37. package/dist/ssh-target.d.ts +19 -0
  38. package/dist/ssh-target.js +58 -0
  39. package/dist/ssh-target.js.map +1 -0
  40. package/dist/tools/register-config-profile-tools.d.ts +2 -0
  41. package/dist/tools/register-config-profile-tools.js +179 -0
  42. package/dist/tools/register-config-profile-tools.js.map +1 -0
  43. package/dist/tools/register-project-tools.d.ts +2 -0
  44. package/dist/tools/register-project-tools.js +377 -0
  45. package/dist/tools/register-project-tools.js.map +1 -0
  46. package/dist/tools/register-session-tools.d.ts +2 -0
  47. package/dist/tools/register-session-tools.js +297 -0
  48. package/dist/tools/register-session-tools.js.map +1 -0
  49. package/dist/tools/register-sftp-tools.d.ts +2 -0
  50. package/dist/tools/register-sftp-tools.js +172 -0
  51. package/dist/tools/register-sftp-tools.js.map +1 -0
  52. package/dist/tools/register-transfer-tools.d.ts +2 -0
  53. package/dist/tools/register-transfer-tools.js +167 -0
  54. package/dist/tools/register-transfer-tools.js.map +1 -0
  55. package/dist/tools/register-tunnel-tools.d.ts +2 -0
  56. package/dist/tools/register-tunnel-tools.js +55 -0
  57. package/dist/tools/register-tunnel-tools.js.map +1 -0
  58. package/dist/tools/tool-metadata.d.ts +18 -0
  59. package/dist/tools/tool-metadata.js +222 -0
  60. package/dist/tools/tool-metadata.js.map +1 -0
  61. package/package.json +5 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.3.3 - 2026-10-25
4
+
5
+ - Add an explicit, optional one-shot InstaBox `ephemeralToken` credential source for `ssh_run_openssh_temporary` and `ssh_run_openssh`; no prior per-token setup script is needed.
6
+ - Preserve local CA fingerprint pinning or native OpenSSH known_hosts verification; an untrusted host is still rejected before sending credentials.
7
+ - Do not expose a one-shot token in native SSH process arguments or returned output; isolate transient config files and clean them up.
8
+ - Keep `usernameEnvVar` as the preferred option when avoiding potentially logged MCP tool arguments, and document the privacy tradeoffs.
9
+ - Add integration coverage for one-shot tokens, response redaction, temporary file cleanup, input validation, and missing CA trust.
10
+
11
+ ## 0.3.2 - 2026-10-25
12
+
13
+ - Refactor MCP tool registration into domain-focused modules while preserving the stdio entry point and existing tool names.
14
+ - Add one-shot native OpenSSH execution and temporary host-CA trust for OpenSSH host-certificate endpoints, with fail-closed local fingerprint pinning and cleanup of temporary trust files.
15
+ - Add host-key/CA probing and an explicit CA trust workflow that checks against a locally configured pin.
16
+ - Add atomic .env hot reload for runtime defaults and SSH profiles; invalid or partial edits keep the last valid snapshot.
17
+ - Redact InstaBox bearer-token usernames from public profile/session results and SSH debug output.
18
+ - Add regression coverage for target validation, host CA parsing, temporary OpenSSH execution/cleanup, and configuration reload.
19
+ - Add a local PowerShell MCP end-to-end test harness. It is maintained in the source repository and is intentionally excluded from the npm package.
package/README.md CHANGED
@@ -100,10 +100,21 @@ npm run build
100
100
  }
101
101
  ```
102
102
 
103
+ 修改 TypeScript 源码后自动重启开发进程:
104
+
105
+ ```powershell
106
+ npm run dev:watch
107
+ ```
108
+
109
+ `dev:watch` 是监视文件后重启进程,不是在运行中的 MCP 进程内替换模块;重启会断开现有 SSH 会话,不应当作生产环境的无中断代码热更新。
110
+
103
111
  ## 能力范围
104
112
 
105
113
  - Profile:`ssh_list_profiles`、`ssh_reload_profiles`、`ssh_connect_profile`、`ssh_check_profile`、`ssh_run_profile`
114
+ - 主机信任:`ssh_probe_host_key`、`ssh_trust_host_ca`
115
+ - 临时 OpenSSH 连接:`ssh_run_openssh_temporary`(独立临时 `known_hosts` 文件,命令结束后删除,不修改用户永久信任文件)
106
116
  - 会话:`ssh_connect`、`ssh_list_sessions`、`ssh_disconnect`、`ssh_disconnect_all`、`ssh_rekey`
117
+ - 原生 OpenSSH 单次命令:`ssh_run_openssh`(适用于需要 OpenSSH 主机证书算法的端点;不创建持久会话)
107
118
  - 命令:`ssh_exec`,支持 env、stdin、PTY、超时、stdout/stderr 最大字节限制、UTF-8/base64 输出
108
119
  - 远程项目编辑:`ssh_project_open`、`ssh_project_tree`、`ssh_project_search`、`ssh_project_stat`、`ssh_file_read`、`ssh_file_patch`、`ssh_project_validate_patch`、`ssh_project_apply_patch`、`ssh_project_diff`、`ssh_project_run_checks`、`ssh_edit_history`、`ssh_edit_rollback`
109
120
  - 交互 shell:`ssh_shell_open`、`ssh_shell_write`、`ssh_shell_read`、`ssh_shell_resize`、`ssh_shell_close`、`ssh_shell_list`
@@ -111,6 +122,105 @@ npm run build
111
122
  - 大文件传输:`sftp_upload_start`、`sftp_upload_profile`、`sftp_download_start`、`sftp_download_profile`、`sftp_transfer_status`、`sftp_transfer_list`、`sftp_transfer_cancel`
112
123
  - 端口转发:`ssh_tunnel_local_start`、`ssh_tunnel_remote_start`、`ssh_tunnel_list`、`ssh_tunnel_stop`
113
124
 
125
+ ## 临时直连未配置的 SSH 设备
126
+
127
+ 不需要先把设备写入 `.env` Profile。调用 `ssh_connect` 时可以直接提供主机、端口和认证信息;会话只保存在当前 MCP server 进程中,连接不会自动写入配置文件。
128
+
129
+ `host` 支持两种形式:
130
+
131
+ 1. 主机和用户名分开:`host="ssh.example.com"`、`username="deploy"`。
132
+ 2. 使用常见的 SSH 目标格式:`host="deploy@ssh.example.com"`,此时可以省略 `username`。
133
+
134
+ 例如,针对平台给出的 `Hostname user@ssh.example.com` 和 `Port 2222`,把 `user@ssh.example.com` 作为 `host`,把 `2222` 作为 `port` 即可。根据服务器的认证配置,可能需要提供 `privateKeyPath`、`privateKey`、`password` 或 `agent`;部分平台会将临时访问令牌直接编码在 SSH 用户名中。
135
+
136
+ 连接示例(建议优先使用本地私钥文件路径,而不是把私钥内容直接放进工具参数):
137
+
138
+ ```json
139
+ {
140
+ "host": "deploy@ssh.example.com",
141
+ "port": 2222,
142
+ "privateKeyPath": "C:/Users/yourname/.ssh/id_ed25519",
143
+ "name": "temporary-lab"
144
+ }
145
+ ```
146
+
147
+ 使用 `ssh_probe_host_key` 可以在不进行用户认证的情况下查看主机指纹;该工具不会自动信任探测到的密钥。兼容的 `ssh2` 连接仍可显式提供 `hostHash` 和 `expectedHostHash`。对于原生 OpenSSH 主机证书,可选择永久登记经过独立核验的 CA,或使用下文的临时信任模式。不要把首次连接成功等同于已完成主机身份验证。需要长期复用的设备,再单独维护为 Profile。
148
+
149
+ ### InstaBox SSH token
150
+
151
+ InstaBox 官方的 Connect → Create token 会生成形如 `ssh ibssh_…@<host>` 的 SSH 命令;token 本身就是 SSH 用户名,不需要额外的 SSH 私钥或密码。token 在被撤销或到期前有效,详见 [InstaBox SSH 文档](https://instabox.sh/docs/ssh)。
152
+
153
+ 为了避免把 bearer token 发送给伪装的 SSH 网关,`ssh_probe_host_key` 可以在不进行认证的情况下检查主机证书和签名 CA 指纹;它不会自动信任探测结果。需要通过可信渠道核验 CA 指纹,不要只因为网络探测成功就自动信任。
154
+
155
+ **当前兼容性限制:** InstaBox 网关提供 `ssh-ed25519-cert-v01@openssh.com` OpenSSH 主机证书,而当前持久会话后端 `ssh2` 不支持这种主机证书算法。程序会在发送 token 前拒绝通过 `ssh_connect` 建立持久会话。可以使用 `ssh_run_openssh` 通过系统 OpenSSH 执行单次命令;它强制 `StrictHostKeyChecking=yes`,要求默认或指定的 `known_hosts` 中已有可信的 CA/主机条目。若只想临时连接,推荐使用 `ssh_run_openssh_temporary`:它读取本地 MCP 进程配置中的 host-specific CA 指纹 pin,重新探测 CA,并要求实时指纹严格匹配,然后使用独立临时 `known_hosts` 文件执行命令,并在结束后删除该文件,不改动用户永久的 `known_hosts`。工具参数不能设置或覆盖这个信任锚;未配置 pin 时会在发起网络探测前拒绝执行。当前 OpenSSH 适配器还不提供持久 Shell、SFTP、后台传输或隧道;这些需要后续统一会话抽象。
156
+
157
+ 本地 CA pin 配置:通过独立可信渠道核验 CA 指纹后,在 MCP 客户端的服务器 `env` 配置中设置 `SSH_MCP_TRUSTED_HOST_CA_FINGERPRINTS`。值是 JSON 对象,键为 `host:port`,值为完整 `SHA256:` 指纹;例如:
158
+
159
+ ```json
160
+ {
161
+ "mcpServers": {
162
+ "enterprise-ssh": {
163
+ "command": "cmd",
164
+ "args": ["/c", "npx", "-y", "enterprise-ssh-mcp-server"],
165
+ "env": {
166
+ "SSH_MCP_TRUSTED_HOST_CA_FINGERPRINTS": "{\"ssh.instabox.sh:2222\":\"SHA256:替换为从可信来源独立核验的完整指纹\"}"
167
+ }
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ 请把示例指纹替换成通过独立可信渠道核验的真实值;不能把 `ssh_probe_host_key` 当前返回值直接复制进配置并将其视为已验证。该环境变量是本地信任锚,工具调用参数不能覆盖它。未配置、JSON 格式错误或指纹格式不正确时,连接和永久信任操作都会失败关闭。
174
+
175
+ 永久信任流程:先按上面步骤配置可信 CA pin,再调用 `ssh_probe_host_key` 检查证书,并在确认需要写入永久信任后调用 `ssh_trust_host_ca`,将探测得到的 `certificateAuthority.knownHostsEntry` 登记到本机 `~/.ssh/known_hosts`。该工具会再次校验 CA 公钥与本地 pin 完全一致;它不会自行建立新的信任锚,也不会覆盖同一主机已存在的冲突 CA。
176
+
177
+ 临时信任流程:无需修改永久 `known_hosts`。在 MCP 客户端环境中配置经过独立核验的 CA pin 后,调用 `ssh_run_openssh_temporary`。可以使用环境变量名 `usernameEnvVar`,或明确选择一次性的 `ephemeralToken`;两者不能同时传入。工具会重新探测实时 CA,并要求其指纹与本地 pin 完全匹配;验证失败时不会运行命令。验证通过后才会创建临时 trust store,命令结束即清理。工具调用不接受 `expectedHostCaFingerprint` 或 `confirmedHostCaFingerprint` 参数,防止模型把未验证的探测结果自我确认。
178
+
179
+ **认证有两种选择。** 默认推荐将 InstaBox Token 存在 MCP 子进程环境,通过 `usernameEnvVar` 引用;新增可选 `ephemeralToken` 允许在某一次 MCP 请求中直接传入凭据,不需要运行预保存脚本,执行后 MCP server 不持久化该 Token。**注意:MCP 客户端、模型平台、工具传输层及调用日志仍可能记录参数;“单次使用”不意味着聊天或工具日志不会保留秘密。** 不要在不可信或有广泛日志留存的聊天/工具链里使用该参数。对于高权限、长期有效的 Token,推荐本机安全凭据存储。直接把 `ibssh_…` token 放进 `host` 或 `username` 参数仍会被拒绝。
180
+
181
+ 两种方式均通过隔离的临时 OpenSSH 配置传入 SSH 用户名,使 Token 不出现在原生 `ssh.exe` 的命令行参数。程序会将 Token 短暂写入系统临时目录中的 OpenSSH 配置文件,并在子进程结束(包括启动失败)后清理;POSIX 系统会以 `0600` 创建文件,Windows 访问保护取决于临时目录 ACL。不要在共享或权限宽松的 Windows 账户环境中使用此方式,也不要把真实 token 写进仓库或共享配置。
182
+
183
+ 示例(仅在已核验并明确确认 InstaBox 主机 CA 指纹后使用;临时信任,不写永久 `known_hosts`):
184
+
185
+ ```json
186
+ {
187
+ "host": "ssh.instabox.sh",
188
+ "usernameEnvVar": "SSH_MCP_INSTABOX_TOKEN",
189
+ "port": 2222,
190
+ "command": "uname -a"
191
+ }
192
+ ```
193
+
194
+ `ephemeralToken` 示例(**演示占位值,不是真实凭据**;仍需事先在本机核验并固定 CA pin):
195
+
196
+ ```json
197
+ {
198
+ "host": "ssh.instabox.sh",
199
+ "port": 2222,
200
+ "ephemeralToken": "ibssh_test_fixture_replace_for_one_request",
201
+ "command": "uname -a"
202
+ }
203
+ ```
204
+
205
+ 其他普通 SSH 主机可以直接调用 `ssh_connect` 并提供 `host`、`port`、`username`、`password` 或私钥信息,获得 `sessionId` 后执行命令/SFTP,再 `ssh_disconnect`。不需要新建 Profile、运行本机脚本或者重新启动 MCP。仅 InstaBox 等使用 OpenSSH 主机证书而不被 `ssh2` 支持的网关,需要用一次性原生 OpenSSH 命令工具;它目前不支持持久交互 Shell/SFTP。
206
+
207
+ `ssh_probe_host_key` 的返回值包含 `certificateAuthority.fingerprint` 和 `certificateAuthority.knownHostsEntry`。前者必须通过可信渠道独立核验后写入本地 pin 配置,后者只有在 CA 身份与 pin 匹配后才应加入 OpenSSH `known_hosts`;探测结果本身不等于信任。
208
+
209
+ InstaBox token 用户名会在 Profile/会话结果和调试日志中脱敏。不要把 token 写入仓库或共享 `.env`;如果 token 已经暴露在聊天、日志或其他不可信位置,请在 InstaBox 中撤销并重新创建。
210
+
211
+ ### 本机真实 MCP 端到端测试
212
+
213
+ 如果要验证的不只是系统 `ssh.exe`,而是 MCP server 本身是否能读取本地 token、检查 CA pin、调用临时 OpenSSH 后端并清理信任文件,可以在**源码仓库根目录**的 Windows PowerShell 中运行(该测试脚本仅随源码仓库维护,不包含在 npm 发布包中):
214
+
215
+ ```powershell
216
+ npm run build
217
+ powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\test-instabox-mcp.ps1
218
+ ```
219
+
220
+ 脚本会在终端中隐藏式读取 `ibssh_` token,将其仅放入本次 MCP 子进程环境,不写入项目文件;然后通过真实 MCP stdio JSON-RPC 调用 `ssh_run_openssh_temporary` 执行只读 `uname -a`,并检查退出码与 `trustStoreDeleted`。结束后会关闭 MCP 子进程并清理本次进程中的凭据引用。
221
+
222
+ 脚本会要求输入 CA 指纹。如果输入独立可信渠道核验后的 `SHA256:...` 指纹,会按该 pin 进行验证;输入 `USE-OBSERVED-ONCE` 则仅用于功能测试,使用此前网络探测到的指纹。**后者不构成 CA 来源的独立身份验证,不应复制到永久配置或 `known_hosts`。** 不要把 Token 输出或粘贴到聊天记录、终端命令参数或仓库中。
223
+
114
224
  ## Agent 使用建议
115
225
 
116
226
  - 远端健康检查、环境确认、服务器巡检:优先使用 `ssh_check_profile`。
@@ -152,6 +262,8 @@ npm run build
152
262
 
153
263
  MCP server 启动时会读取项目根目录的 `.env`。如果你的 MCP client 从其他目录启动,也可以用 `SSH_MCP_ENV_FILE` 指定绝对路径。
154
264
 
265
+ 默认会监视 `.env` 文件:保存后约 1 秒内会尝试重新加载运行默认值和所有 Profile,解析成功后作为一个完整快照应用;编辑器保存过程中的半成品或非法配置会被拒绝,上一份有效配置继续生效。也可以调用 `ssh_reload_config` 或 `ssh_reload_profiles` 立即触发同样的原子重载。修改 `SSH_MCP_TOOLSET` 或 `SSH_MCP_ENABLE_DANGEROUS_TOOLS` 后仍需重启 MCP server,因为工具暴露集合在启动时注册。
266
+
155
267
  运行时默认策略:
156
268
 
157
269
  ```dotenv
@@ -0,0 +1,4 @@
1
+ export declare function createApp(): {
2
+ server: import("@modelcontextprotocol/sdk/server/mcp").McpServer;
3
+ shutdown: () => Promise<void>;
4
+ };
@@ -0,0 +1,38 @@
1
+ import { toolContext } from "./tool-context.js";
2
+ import { startEnvHotReload } from "../env-reload.js";
3
+ import { registerConfigProfileTools } from "../tools/register-config-profile-tools.js";
4
+ import { registerProjectTools } from "../tools/register-project-tools.js";
5
+ import { registerSessionTools } from "../tools/register-session-tools.js";
6
+ import { registerSftpTools } from "../tools/register-sftp-tools.js";
7
+ import { registerTransferTools } from "../tools/register-transfer-tools.js";
8
+ import { registerTunnelTools } from "../tools/register-tunnel-tools.js";
9
+ export function createApp() {
10
+ registerConfigProfileTools(toolContext);
11
+ registerProjectTools(toolContext);
12
+ registerSessionTools(toolContext);
13
+ registerSftpTools(toolContext);
14
+ registerTransferTools(toolContext);
15
+ registerTunnelTools(toolContext);
16
+ const stopEnvHotReload = startEnvHotReload({
17
+ envPath: toolContext.envPath,
18
+ reload: toolContext.reloadEnvState,
19
+ onReload: (snapshot) => {
20
+ const restartNote = snapshot.restartRequiredForToolVisibility
21
+ ? "; tool visibility settings differ from startup and require an MCP restart"
22
+ : "";
23
+ process.stderr.write(`[ssh-mcp] .env hot-reloaded (${snapshot.profiles.length} profile(s))${restartNote}\n`);
24
+ },
25
+ onError: (error) => {
26
+ const message = error instanceof Error ? error.message : "unknown configuration error";
27
+ process.stderr.write(`[ssh-mcp] .env hot reload rejected; previous settings remain active (${message})\n`);
28
+ },
29
+ });
30
+ return {
31
+ server: toolContext.server,
32
+ shutdown: async () => {
33
+ stopEnvHotReload();
34
+ await toolContext.sessions.disconnectAll();
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=create-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-app.js","sourceRoot":"","sources":["../../src/app/create-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AACvF,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAExE,MAAM,UAAU,SAAS;IACvB,0BAA0B,CAAC,WAAW,CAAC,CAAC;IACxC,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAClC,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAClC,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC/B,qBAAqB,CAAC,WAAW,CAAC,CAAC;IACnC,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAEjC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;QACzC,OAAO,EAAE,WAAW,CAAC,OAAO;QAC5B,MAAM,EAAE,WAAW,CAAC,cAAc;QAClC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE;YACrB,MAAM,WAAW,GAAG,QAAQ,CAAC,gCAAgC;gBAC3D,CAAC,CAAC,2EAA2E;gBAC7E,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gCAAgC,QAAQ,CAAC,QAAQ,CAAC,MAAM,eAAe,WAAW,IAAI,CACvF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;YACvF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wEAAwE,OAAO,KAAK,CAAC,CAAC;QAC7G,CAAC;KACF,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,WAAW,CAAC,MAAM;QAC1B,QAAQ,EAAE,KAAK,IAAmB,EAAE;YAClC,gBAAgB,EAAE,CAAC;YACnB,MAAM,WAAW,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,258 @@
1
+ import { McpServer, type RegisteredTool, type ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { AnySchema, ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js";
3
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
4
+ import { z } from "zod";
5
+ import type { PseudoTtyOptions } from "ssh2";
6
+ import { ProfileRegistry, type SshServerProfile } from "../env-profiles.js";
7
+ import { RuntimeConfigRegistry } from "../runtime-config.js";
8
+ import { SessionRegistry } from "../session-registry.js";
9
+ import type { ExecResult, SessionSummary, SshSession } from "../ssh-session.js";
10
+ import { entryToJson, sftpChmod, sftpChown, sftpMkdir, sftpMkdirRecursive, sftpReadFileLimited, sftpReadDir, sftpReadlink, sftpRealpath, sftpRemove, sftpRename, sftpStat, sftpSymlink, sftpUtimes, sftpWriteFile, statsToJson } from "../sftp-ops.js";
11
+ import { TransferManager } from "../transfer-manager.js";
12
+ import { assertNonEmpty, McpToolError, setCompactJsonResponse, toToolResult } from "../tool-result.js";
13
+ import { defaultTreeOptions, maxEditFileBytes, parseUnifiedPatch, publicSnapshot, readRemoteFileSnapshot, readRemoteTree, resolveSafeRemotePath, sha256, type BackupRecord } from "../remote-edit.js";
14
+ declare const ptySchema: z.ZodObject<{
15
+ rows: z.ZodOptional<z.ZodNumber>;
16
+ cols: z.ZodOptional<z.ZodNumber>;
17
+ height: z.ZodOptional<z.ZodNumber>;
18
+ width: z.ZodOptional<z.ZodNumber>;
19
+ term: z.ZodOptional<z.ZodString>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ rows?: number | undefined;
22
+ cols?: number | undefined;
23
+ height?: number | undefined;
24
+ width?: number | undefined;
25
+ term?: string | undefined;
26
+ }, {
27
+ rows?: number | undefined;
28
+ cols?: number | undefined;
29
+ height?: number | undefined;
30
+ width?: number | undefined;
31
+ term?: string | undefined;
32
+ }>;
33
+ declare const expectedFileSchema: z.ZodObject<{
34
+ path: z.ZodString;
35
+ sha256: z.ZodUnion<[z.ZodString, z.ZodLiteral<"absent">]>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ path: string;
38
+ sha256: string;
39
+ }, {
40
+ path: string;
41
+ sha256: string;
42
+ }>;
43
+ type ToolConfig<OutputArgs extends ZodRawShapeCompat | AnySchema, InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined> = {
44
+ title?: string;
45
+ description?: string;
46
+ inputSchema?: InputArgs;
47
+ outputSchema?: OutputArgs;
48
+ annotations?: ToolAnnotations;
49
+ _meta?: Record<string, unknown>;
50
+ };
51
+ declare const remoteCheckNameSchema: z.ZodEnum<["identity", "pwd", "os", "uptime", "disk", "memory", "processes"]>;
52
+ type RemoteCheckName = z.infer<typeof remoteCheckNameSchema>;
53
+ declare function registerTool<OutputArgs extends ZodRawShapeCompat | AnySchema, InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined>(name: string, config: ToolConfig<OutputArgs, InputArgs>, cb: ToolCallback<InputArgs>): RegisteredTool | undefined;
54
+ type ExpectedFile = z.infer<typeof expectedFileSchema>;
55
+ interface ProjectWorkspace {
56
+ profile: SshServerProfile;
57
+ sessionId: string;
58
+ session: SshSession;
59
+ sftp: Awaited<ReturnType<SshSession["getSftp"]>>;
60
+ rootPath: string;
61
+ }
62
+ type EditOperation = "create" | "modify" | "delete" | "move";
63
+ interface EditFileChange {
64
+ path: string;
65
+ remotePath: string;
66
+ operation: EditOperation;
67
+ oldSha256?: string | undefined;
68
+ newSha256?: string | undefined;
69
+ bytes?: number | undefined;
70
+ backup?: BackupRecord | undefined;
71
+ movedToPath?: string | undefined;
72
+ movedToRemotePath?: string | undefined;
73
+ }
74
+ interface EditRecord {
75
+ editId: string;
76
+ profileName: string;
77
+ projectRoot: string;
78
+ createdAt: string;
79
+ rolledBackAt?: string;
80
+ changes: EditFileChange[];
81
+ }
82
+ declare function connectProjectWorkspace(profileName: string, projectRoot: string | undefined, requireEdit: boolean): Promise<ProjectWorkspace>;
83
+ declare function inspectRemoteProject(session: SshSession, rootPath: string): Promise<object>;
84
+ declare function projectProfileSummary(profile: SshServerProfile): object;
85
+ declare function writeProjectFile(workspace: ProjectWorkspace, path: string, data: Buffer, expectedSha256: string | "absent" | undefined, createDirs: boolean, mode: number | undefined): Promise<EditRecord>;
86
+ declare function applySinglePatchFile(workspace: ProjectWorkspace, path: string, patchFile: ReturnType<typeof parseUnifiedPatch>[number], expectedSha256: string | "absent", createDirs: boolean): Promise<EditRecord>;
87
+ declare function validateProjectPatch(workspace: ProjectWorkspace, patchFiles: ReturnType<typeof parseUnifiedPatch>, expectedFiles: ExpectedFile[]): Promise<object>;
88
+ declare function applyProjectPatch(workspace: ProjectWorkspace, patchFiles: ReturnType<typeof parseUnifiedPatch>, expectedFiles: ExpectedFile[], createDirs: boolean): Promise<EditRecord>;
89
+ declare function deleteProjectFile(workspace: ProjectWorkspace, path: string, expectedSha256: string): Promise<EditRecord>;
90
+ declare function moveProjectFile(workspace: ProjectWorkspace, fromPath: string, toPath: string, expectedSha256: string, createDirs: boolean): Promise<EditRecord>;
91
+ declare function rollbackEdit(workspace: ProjectWorkspace, edit: EditRecord): Promise<EditRecord>;
92
+ declare function resolveProjectPathArgs(workspace: ProjectWorkspace, paths: string[], mustExist: boolean): Promise<string[]>;
93
+ declare function assertPatchSize(patch: string): void;
94
+ declare function compactTreeOptions(maxDepth: number | undefined, maxEntries: number | undefined, includeHidden: boolean | undefined, ignoreNames: string[] | undefined): Parameters<typeof defaultTreeOptions>[0];
95
+ declare function buildProjectSearchCommand(rootPath: string, query: string, paths: string[], literal: boolean, maxResults: number, contextLines: number): string;
96
+ declare function buildProjectDiffCommand(rootPath: string, paths: string[]): string;
97
+ declare function parseSearchMatches(output: string): Array<{
98
+ path: string;
99
+ line?: number;
100
+ text: string;
101
+ }>;
102
+ declare function normalizeProfileNameForHistory(profileName: string): string;
103
+ interface ProfileConnectionInput {
104
+ profileName: string;
105
+ keyboardResponses: string[];
106
+ strictVendor: boolean;
107
+ debug: boolean;
108
+ sessionName?: string | undefined;
109
+ agentForward?: boolean | undefined;
110
+ tryKeyboard?: boolean | undefined;
111
+ keepaliveIntervalMs?: number | undefined;
112
+ keepaliveCountMax?: number | undefined;
113
+ readyTimeoutMs?: number | undefined;
114
+ connectionTimeoutMs?: number | undefined;
115
+ hostHash?: "md5" | "sha1" | "sha256" | undefined;
116
+ expectedHostHash?: string | undefined;
117
+ }
118
+ interface ConnectedProfileSession {
119
+ profile: {
120
+ name: string;
121
+ aliases: string[];
122
+ host: string;
123
+ port: number;
124
+ username: string;
125
+ displayName?: string;
126
+ defaultDir?: string;
127
+ description?: string;
128
+ platform?: string;
129
+ };
130
+ session: SessionSummary;
131
+ }
132
+ declare function connectProfileSession(input: ProfileConnectionInput): Promise<ConnectedProfileSession>;
133
+ declare function buildRemoteCommand(command: string, workingDirectory: string | undefined): string;
134
+ interface RemoteCheckInput {
135
+ name: RemoteCheckName;
136
+ title: string;
137
+ command: string;
138
+ timeoutMs: number;
139
+ maxOutputBytes: number;
140
+ execute: (command: string) => Promise<ExecResult>;
141
+ }
142
+ declare function runRemoteCheck(input: RemoteCheckInput): Promise<{
143
+ name: RemoteCheckName;
144
+ title: string;
145
+ command: string;
146
+ ok: boolean;
147
+ result?: ExecResult;
148
+ error?: string;
149
+ }>;
150
+ declare function compactExecPty(input: boolean | z.infer<typeof ptySchema> | undefined): PseudoTtyOptions | boolean | undefined;
151
+ declare function compactPty(input: z.infer<typeof ptySchema> | undefined): PseudoTtyOptions | undefined;
152
+ export declare const toolContext: {
153
+ server: McpServer;
154
+ envPath: string;
155
+ sessions: SessionRegistry;
156
+ z: typeof z;
157
+ setCompactJsonResponse: typeof setCompactJsonResponse;
158
+ toToolResult: typeof toToolResult;
159
+ profiles: ProfileRegistry;
160
+ runtimeConfig: RuntimeConfigRegistry;
161
+ reloadEnvState: () => import("../env-reload.js").EnvReloadSnapshot;
162
+ wireEncodingSchema: z.ZodDefault<z.ZodEnum<["utf8", "base64"]>>;
163
+ remoteCheckNameSchema: z.ZodEnum<["identity", "pwd", "os", "uptime", "disk", "memory", "processes"]>;
164
+ DEFAULT_REMOTE_CHECKS: ("identity" | "pwd" | "os" | "uptime" | "disk" | "memory" | "processes")[];
165
+ REMOTE_CHECK_COMMANDS: Record<"identity" | "pwd" | "os" | "uptime" | "disk" | "memory" | "processes", {
166
+ title: string;
167
+ command: string;
168
+ }>;
169
+ registerTool: typeof registerTool;
170
+ connectProfileSession: typeof connectProfileSession;
171
+ buildRemoteCommand: typeof buildRemoteCommand;
172
+ runRemoteCheck: typeof runRemoteCheck;
173
+ sftpStat: typeof sftpStat;
174
+ statsToJson: typeof statsToJson;
175
+ McpToolError: typeof McpToolError;
176
+ defaultTreeOptions: typeof defaultTreeOptions;
177
+ maxEditFileBytes: typeof maxEditFileBytes;
178
+ parseUnifiedPatch: typeof parseUnifiedPatch;
179
+ publicSnapshot: typeof publicSnapshot;
180
+ readRemoteFileSnapshot: typeof readRemoteFileSnapshot;
181
+ readRemoteTree: typeof readRemoteTree;
182
+ resolveSafeRemotePath: typeof resolveSafeRemotePath;
183
+ sha256: typeof sha256;
184
+ editHistory: Map<string, EditRecord>;
185
+ projectRootSchema: z.ZodOptional<z.ZodString>;
186
+ fileEncodingSchema: z.ZodDefault<z.ZodEnum<["utf8", "base64"]>>;
187
+ expectedShaSchema: z.ZodUnion<[z.ZodString, z.ZodLiteral<"absent">]>;
188
+ expectedFileSchema: z.ZodObject<{
189
+ path: z.ZodString;
190
+ sha256: z.ZodUnion<[z.ZodString, z.ZodLiteral<"absent">]>;
191
+ }, "strip", z.ZodTypeAny, {
192
+ path: string;
193
+ sha256: string;
194
+ }, {
195
+ path: string;
196
+ sha256: string;
197
+ }>;
198
+ projectPathSchema: z.ZodString;
199
+ connectProjectWorkspace: typeof connectProjectWorkspace;
200
+ inspectRemoteProject: typeof inspectRemoteProject;
201
+ projectProfileSummary: typeof projectProfileSummary;
202
+ writeProjectFile: typeof writeProjectFile;
203
+ applySinglePatchFile: typeof applySinglePatchFile;
204
+ validateProjectPatch: typeof validateProjectPatch;
205
+ applyProjectPatch: typeof applyProjectPatch;
206
+ deleteProjectFile: typeof deleteProjectFile;
207
+ moveProjectFile: typeof moveProjectFile;
208
+ rollbackEdit: typeof rollbackEdit;
209
+ resolveProjectPathArgs: typeof resolveProjectPathArgs;
210
+ assertPatchSize: typeof assertPatchSize;
211
+ compactTreeOptions: typeof compactTreeOptions;
212
+ buildProjectSearchCommand: typeof buildProjectSearchCommand;
213
+ buildProjectDiffCommand: typeof buildProjectDiffCommand;
214
+ parseSearchMatches: typeof parseSearchMatches;
215
+ normalizeProfileNameForHistory: typeof normalizeProfileNameForHistory;
216
+ sessionIdSchema: z.ZodString;
217
+ ptySchema: z.ZodObject<{
218
+ rows: z.ZodOptional<z.ZodNumber>;
219
+ cols: z.ZodOptional<z.ZodNumber>;
220
+ height: z.ZodOptional<z.ZodNumber>;
221
+ width: z.ZodOptional<z.ZodNumber>;
222
+ term: z.ZodOptional<z.ZodString>;
223
+ }, "strip", z.ZodTypeAny, {
224
+ rows?: number | undefined;
225
+ cols?: number | undefined;
226
+ height?: number | undefined;
227
+ width?: number | undefined;
228
+ term?: string | undefined;
229
+ }, {
230
+ rows?: number | undefined;
231
+ cols?: number | undefined;
232
+ height?: number | undefined;
233
+ width?: number | undefined;
234
+ term?: string | undefined;
235
+ }>;
236
+ compactExecPty: typeof compactExecPty;
237
+ compactPty: typeof compactPty;
238
+ entryToJson: typeof entryToJson;
239
+ sftpChmod: typeof sftpChmod;
240
+ sftpChown: typeof sftpChown;
241
+ sftpMkdir: typeof sftpMkdir;
242
+ sftpMkdirRecursive: typeof sftpMkdirRecursive;
243
+ sftpReadFileLimited: typeof sftpReadFileLimited;
244
+ sftpReadDir: typeof sftpReadDir;
245
+ sftpReadlink: typeof sftpReadlink;
246
+ sftpRealpath: typeof sftpRealpath;
247
+ sftpRemove: typeof sftpRemove;
248
+ sftpRename: typeof sftpRename;
249
+ sftpSymlink: typeof sftpSymlink;
250
+ sftpUtimes: typeof sftpUtimes;
251
+ sftpWriteFile: typeof sftpWriteFile;
252
+ modeSchema: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
253
+ assertNonEmpty: typeof assertNonEmpty;
254
+ transfers: TransferManager;
255
+ positivePortSchema: z.ZodNumber;
256
+ };
257
+ export type ToolContext = typeof toolContext;
258
+ export {};