Skip to main content
Glama

sshmcp

让 AI 通过 SSH 操作你的服务器,但全程看不到你的凭据。

AI 只能看到主机别名,用别名下发命令和文件操作。私钥、登录密码、私钥口令、sudo 密码 存在本机的加密 vault 里,由你在终端管理;sshmcp 的任何工具都不接受、不返回、不记录这些值。

  • 20 个工具:执行命令、批量执行、持久 shell 会话、命令模板、日志 tail、文件读写、精确替换、目录增删、移动、上传下载

  • 跨平台:Windows / Linux / macOS 同一套加密 vault,Linux 不需要 dbus 或 SecretService

  • 双传输:stdio(客户端拉起本地进程)或 streamable HTTP(带 Bearer 鉴权)

  • 命令完全放开,只做全量审计;不拦 rm -rf(见下方风险提示)

  • 341 项测试,其中端到端门禁跑真实 SSH 协议 + 真实 MCP JSON-RPC,CI 覆盖 Windows 与 Linux

  • 密码明文输入:提示直接显示输入内容,粘贴照常可用,绕开 Windows getpass 在 Git Bash 下读不到粘贴的坑


安全边界:AI 能看到什么,看不到什么

AI 可见

AI 不可见

主机

别名、描述、标签;host/port/user(可配置隐藏)

私钥路径、vault 内容

认证

配了哪几种方式(key_file / agent / vault)

私钥内容、密码、口令、sudo 密码

命令

自己下发的命令、exit code、stdout/stderr

注入 stdin 的 sudo 密码

文件

远端文件内容(脱敏后)

其中的凭据与私钥块 → [REDACTED]

本地

local_roots 内的上传下载

state 目录、~/.ssh(硬禁,即使写进 local_roots)

主机密钥

已固定的指纹

无「接受新密钥」的工具,密钥变更只能由你处理

实现上的四条硬约束:

  1. 工具签名里没有任何凭据参数,也不存在名字含 vault / config / credential / secret / key / token / audit 的工具。这两点有专门的测试断言。

  2. 凭据管理只有 CLI 一条路,秘密通过明文提示或剪贴板输入,不进命令行参数、不进 shell 历史。

  3. 所有出口过脱敏器:stdout/stderr、文件内容、异常消息、审计日志。启动时把 vault 里全部秘密值注册为字面量,另有正则拦截 PEM / OpenSSH / PuTTY 私钥块(含被输出截断后只剩头部的残块)。

  4. 主机密钥 TOFU 固定在独立的 known_hosts,指纹变更直接拒绝连接。

审计记录每次连接、命令、文件操作,以及被拒绝的连接(主机密钥变更、认证失败)和所有报错的工具调用。

这个保证的边界在哪(请务必读)

sshmcp 保证的是「它自己不把凭据交给模型」。它不能约束你的 MCP 客户端还给 AI 开了什么别的工具。

如果你的客户端同时给了 AI 本地 shell 或任意文件读取能力,AI 理论上可以读到主密码文件, 再自己解密 vault。要把这条路也堵上,选一个:

  • 最彻底:不用 vault,改用 --identity-file + ssh-agent。私钥完全不经过 sshmcp, 主密码这种东西根本不存在。

  • 把 state 目录放在 AI 工作区之外,并且不给 AI 该目录的读权限。

  • 只给 AI sshmcp 这一组工具,不给本地 shell。

另外几点需要知道:

  • 解密后的凭据在进程内存中。Python 无法可靠擦除字符串,这是语言层面的限制。

  • Windows 上文件权限继承目录 ACL,不像 POSIX 的 0600 那样强制。state 目录请放在你自己的用户目录下。

  • redact_private_keys: false 只放开「vault 不认识的」私钥;vault 里自己的凭据始终脱敏。

  • 密码短于 4 个字符时,脱敏会替换掉输出里所有同样的片段,可能让输出很难读。CLI 会警告。

命令权限风险

按你的选择,命令面完全放开:AI 可以执行 rm -rf /、mkfs、shutdown。 sshmcp 不拦,只记账。降低风险的做法:

  • 用低权限账号登录,需要提权时才 sudo=true,并单独存 sudo 密码

  • 给关键主机设 host_key_policy: "strict"

  • 定期看 sshmcp audit tail

  • 在远端用 authorized_keys 的 command= / restrict 选项做服务端限制


Related MCP server: SSH MCP Server

安装

需要 Python 3.11+。

git clone https://github.com/boyunxi/sshmcp.git sshmcp && cd sshmcp
python -m venv .venv

Windows(Git Bash):

source .venv/Scripts/activate
pip install -e .

Linux / macOS:

source .venv/bin/activate
pip install -e .

装完有一个 sshmcp 命令,也可以用 python -m sshmcp。


快速开始

1. 初始化

sshmcp init

会创建 state 目录、config.json 和加密 vault,并让你设一个主密码(至少 8 位)。

只想用 ssh-agent 或现有私钥文件、不想管主密码:

sshmcp init --no-vault

2. 加一台主机

密码登录(--password 是触发交互提示的开关,不是密码本身):

sshmcp host add prod-web --hostname 203.0.113.10 --user deploy --password \
  --description "生产 Web" --tag prod

用现有私钥文件(只存路径,私钥仍由 ssh 自己读):

sshmcp host add prod-web --hostname 203.0.113.10 --user deploy \
  --identity-file ~/.ssh/id_ed25519

把私钥导入加密 vault(之后原文件可以不在本机):

sshmcp host add prod-web --hostname 203.0.113.10 --user deploy \
  --private-key ~/.ssh/id_ed25519 --passphrase

需要 sudo 的话补一条(对已存在的别名是合并更新,只改动你给出的字段,其它保持原样):

sshmcp host add prod-web --sudo-password

脚本化场景用 JSON 从 stdin 喂:

echo '{"password":"...","sudo_password":"..."}' | \
  sshmcp host add prod-web --hostname 203.0.113.10 --user deploy --secrets-stdin

3. 验证连通

sshmcp host test prod-web
sshmcp doctor

host test 首次连接会按 TOFU 固定主机密钥。想显式核对指纹再固定:

sshmcp host trust prod-web --fingerprint SHA256:xxxxx

4. 接到 MCP 客户端

sshmcp mcp-config

输出可直接贴进客户端配置。stdio 模式不能交互输入主密码(stdin 被 JSON-RPC 占用), 所以生成的配置用 SSHMCP_VAULT_PASSWORD_FILE 指向一个密码文件。按提示创建它:

# state 目录路径见 sshmcp paths
printf '%s' '你的主密码' > ~/.config/sshmcp/vault-password
chmod 600 ~/.config/sshmcp/vault-password

Windows 上路径是 %APPDATA%\sshmcp\vault-password,请把该文件的权限限制为只有你的账号。

生成的配置形如:

{
  "mcpServers": {
    "sshmcp": {
      "command": "/path/to/.venv/bin/python",
      "args": ["-m", "sshmcp", "serve", "--transport", "stdio", "--home", "/path/to/state"],
      "env": { "SSHMCP_VAULT_PASSWORD_FILE": "/path/to/state/vault-password" }
    }
  }
}

Qoder / Claude Desktop / Cursor 等支持 mcpServers 格式的客户端都能直接用。 接好后让 AI 先调 ssh_hosts 看有哪些别名。

HTTP 模式

想让多个客户端或远程 IDE 连同一个实例:

sshmcp serve --transport http --http-host 127.0.0.1 --http-port 8765
sshmcp token show          # 取 Bearer token
sshmcp mcp-config --transport http

默认只绑回环地址。要绑非回环地址,必须在 config.json 的 http.allowed_hosts 里写上客户端会用的域名,否则 DNS rebinding 保护会拒掉请求(返回 421)。


密码输入

密码提示是明文的:直接读一行,输入的字符会显示在屏幕上,粘贴照常可用。

sshmcp init
# vault 主密码:███
# 再次输入主密码:███

sshmcp host add prod-web --hostname 203.0.113.10 --user deploy --password
# 登录密码:███

CLI 的所有提示、状态输出和帮助文本都是中文;底层异常(vault / 配置 / SSH 层)保持英文, 因为同一批消息也会作为工具结果返回给 MCP 客户端。

之所以不用隐藏输入:Windows 上 getpass 绑定的是 win_getpass,用 msvcrt.getwch() 从控制台 CONIN$ 逐字符读,而不是从 sys.stdin 读。在 Git Bash / mintty 里 stdin 是管道, 你粘贴的内容进了管道,getwch() 却在等控制台输入——粘贴会丢失,或者直接卡死。 明文读一行在所有终端下都稳。

需要知道的取舍:

  • 密码会显示在屏幕上,并留在终端滚动缓冲里。别在录屏或共享会话里用。

  • 不会进 shell 历史,也不会出现在命令行参数里(--password 只是触发提示的开关)。

  • 提示语走 stderr,所以 mcp-config 之类输出 JSON 的命令不会被污染。

如果你不想让它出现在滚动缓冲里,用剪贴板:

# 主机密码 / 私钥口令 / sudo 密码,各自独立
sshmcp host add prod-web --hostname 203.0.113.10 --user deploy --password-clipboard
sshmcp host add prod-web --sudo-password-clipboard
sshmcp host add prod-web --passphrase-clipboard

# vault 主密码(--clipboard 是全局选项,适用于任何需要解锁的命令)
sshmcp init --clipboard
sshmcp host show prod-web --clipboard
sshmcp vault passwd --new-clipboard      # 新主密码从剪贴板读

后端:Windows 用 ctypes 调原生 Win32 剪贴板 API(零新依赖);macOS 用 pbpaste; Linux 依次尝试 wl-paste(Wayland)、xclip、xsel。都没有时会报错,去掉 --clipboard 直接输入即可。

剪贴板只去掉末尾的换行,不会动密码本身首尾的空格。读到的值不会出现在任何输出或错误信息里, 只告诉你读到了多少个字符,方便你确认复制对了。

注意:读完后密码仍留在剪贴板里。sshmcp 不会替你清空——那会破坏你的剪贴板内容。

什么时候会提示

场景

行为

解锁已有 vault

--vault-password-file / SSHMCP_VAULT_PASSWORD_FILE / SSHMCP_VAULT_PASSWORD 有值就不提示;否则明文提示

init 新建 vault

不读 env(你要设的是新密码)。用 --vault-password-file 可免二次确认,否则提示两次

vault passwd 改主密码

提示两次(新密码 + 确认),或 --new-clipboard

--clipboard

明确指定来源,任何情况下都优先于 env

JSON 从 stdin 喂(脚本化)

echo '{"password":"...","sudo_password":"..."}' | \
  sshmcp host add prod-web --hostname 203.0.113.10 --user deploy --secrets-stdin

--secrets-stdin 不能和提示类或剪贴板类开关混用;同一个字段的 --password 和 --password-clipboard 也互斥,都会直接报错而不是静默选一个。

serve 拒绝 --clipboard——服务进程不该交互读秘密,请用密码文件或环境变量。


工具清单

多数工具的第一个参数是 host(别名);ssh_batch_exec 收 hosts 列表, ssh_session_send / ssh_session_close 收 session_id,ssh_templates 和 ssh_hosts 无参数。

工具

说明

ssh_hosts

列出别名与非秘密元数据。先调这个

ssh_ping

连通性、远端 uname、已固定的主机密钥指纹

ssh_exec

执行命令。timeout / cwd / sudo / stdin / max_output_bytes

ssh_batch_exec

一次对多台主机跑同一命令,逐台返回结果 + 成功/失败汇总

ssh_session_open

开一个持久 /bin/sh,拿 session_id。可选 cwd

ssh_session_send

在会话里跑一条命令,状态(cd/export)保留,返回 stdout / stderr / exit_code

ssh_session_close

关闭会话,返回执行过的命令数

ssh_templates

列出操作员在 config 里定义的命令模板

ssh_run_template

执行模板;参数值会被 shell 转义

ssh_read_file

读文本文件。offset / max_bytes / encoding。二进制文件会被拒绝

ssh_tail

读文件最后 N 行(默认 100,最多 5000)。二进制拒绝

ssh_write_file

写文件。content 与 content_base64 二选一;mode = overwrite | append | create;backup / mkdir_parents / permissions

ssh_edit_file

精确字符串替换。命中 0 次报错;命中多次需 replace_all=true

ssh_list_dir

列目录,返回 name/type/size/mode/uid/gid/mtime。可用 pattern glob

ssh_stat

路径是否存在及元信息;符号链接会带 symlink_target

ssh_mkdir

建目录,parents 默认 true

ssh_remove

删文件或目录。非空目录需 recursive=true

ssh_move

移动/重命名。目标已存在需 overwrite=true

ssh_upload

本地 → 远端。单文件或 recursive=true 整个目录;resume / verify。本地侧受 local_roots 约束

ssh_download

远端 → 本地。本地侧受 local_roots 约束

几个行为细节:

  • ssh_exec 的 max_output_bytes 是每条流各自的上限(stdout 和 stderr 分开算)。超限时继续 drain 到命令结束,所以 exit code 仍然有效,只是 truncated=true。

  • timeout 必须大于 0;省略则用主机的 command_timeout。max_output_bytes / ssh_read_file 的 max_bytes 上限 8 MiB,stdin 上限 8 MiB——更大的内容请分块读或走 ssh_upload。

  • ssh_write_file 的 content 只收 UTF-8 文本;图片、压缩包这类非文本内容用 content_base64(两者互斥,解码后上限 8 MiB)。返回里的 content_encoding 会告诉你是哪种。

  • ssh_batch_exec 最多 50 台主机、最多 8 台并发;单台失败不影响其它,失败项带 error 字段。整体耗时是并行后的墙钟时间。

  • 持久会话:ssh_session_open 起一个 /bin/sh,cd、export 等在后续 ssh_session_send 里保留。空闲 15 分钟自动回收,累计 500 条命令后拒绝继续,每台主机最多 4 个会话。一条命令超时会直接销毁会话——超时后剩下的输出无法归属,继续复用会把脏数据算到下一条命令头上。

  • ssh_tail 从文件尾部读最多 256 KiB,按行切出最后 N 行;文件更早的内容不会加载。

  • 命令模板由操作员写在 config.json 的 templates 里,AI 只能执行不能新增。参数值一律 shlex.quote,所以模板里不要自己加引号。模板可以限定 hosts(支持 glob)、带 sudo 和 timeout。

  • 配置热加载:改完 config.json 最多 1 秒生效,新增/修改/删除的主机会自动断开旧连接,不影响其它主机;改坏文件时保留旧配置并在审计里记 config.reload_failed。vault 文件变化会用已派生的密钥重读(host add 新增的密码立即生效),但 vault passwd 换了 salt 之后需要重启服务。

  • ssh_upload 的 recursive=true 上传整个目录:跳过符号链接(不跟随,避免逃出目录树)、尽力保留文件权限位、整棵树上限 10000 个文件,返回 files / directories / skipped_symlinks。

  • ssh_upload 的 resume=true 从远端已有字节数继续传(远端比本地大时直接拒绝);verify=true 传完比对 sha256,主机没有 sha256sum / shasum 时返回 verified=false + verify_error 而不是假成功。resume / verify 只用于单文件,目录上传会拒绝。

  • 超时会关闭通道(远端命令随之终止),返回 timed_out=true、exit_code=null。传输层故障只在命令尚未发出时重试一次;已经发出后不再重发,避免同一条命令被执行两次。

  • sudo=true 时先探测是否需要密码。需要则走 sudo -S -p '' -- /bin/sh -c '<整条命令>', 密码只作为 stdin 首行注入,不分配 PTY(避免回显)。用 sh -c 包裹是为了让 sudo 覆盖整条命令,包括 && 和管道。

  • ssh_move 的 overwrite=true 依赖服务端的 posix-rename 扩展(OpenSSH 支持)。


配置

config.json 在 state 目录里,不含任何秘密。未知字段会直接报错,避免拼错被静默忽略。

{
  "version": 1,
  "defaults": {
    "port": 22,
    "connect_timeout": 15.0,
    "command_timeout": 60.0,
    "keepalive": 30.0,
    "max_output_bytes": 262144,
    "host_key_policy": "tofu"
  },
  "security": {
    "local_roots": [],
    "redact_private_keys": true,
    "expose_connection_details": true
  },
  "http": {
    "host": "127.0.0.1",
    "port": 8765,
    "allowed_hosts": []
  },
  "audit": {
    "enabled": true,
    "max_bytes": 5242880,
    "keep": 5
  },
  "templates": {
    "svc-restart": {
      "command": "systemctl restart {service} && systemctl is-active {service}",
      "description": "重启服务并确认状态",
      "params": ["service"],
      "sudo": true,
      "hosts": ["prod-*"],
      "timeout": 90
    }
  },
  "hosts": {
    "prod-web": {
      "hostname": "203.0.113.10",
      "user": "deploy",
      "port": 22,
      "identity_file": "~/.ssh/id_ed25519",
      "allow_agent": true,
      "host_key_policy": "strict",
      "description": "生产 Web",
      "tags": ["prod", "web"]
    }
  }
}
  • defaults 里的值会被各主机继承,主机里同名字段覆盖它。

  • security.local_roots 为空时用进程当前工作目录。state 目录和 ~/.ssh 始终被禁, 即使显式写进 local_roots 也不行;路径先 resolve() 再判断,.. 和符号链接都逃不出去。

  • security.expose_connection_details: false 可以让 ssh_hosts 只返回别名/描述/标签, 连 host/port/user 都不给 AI。

  • host_key_policy:tofu 首次连接自动固定;strict 必须先 sshmcp host trust。

  • allow_agent: false 禁止该主机尝试 ssh-agent。look_for_keys 恒为 false—— 绝不会隐式捡起你没为该主机声明的 ~/.ssh 私钥。

  • templates 里每个模板必须有 command 和 params;command 里用到的占位符必须全部声明, 声明了但没用到也会报错(防拼错)。hosts 留空表示所有主机可用,支持 glob。 参数值在渲染时会被 shlex.quote,模板里不要再加引号。

state 目录位置

优先级:--home > $SSHMCP_HOME > 平台默认。

平台

默认位置

Windows

%APPDATA%\sshmcp

Linux

$XDG_CONFIG_HOME/sshmcp,否则 ~/.config/sshmcp

macOS

~/Library/Application Support/sshmcp

里面有 config.json、vault.enc、known_hosts、audit.jsonl、http_token。 sshmcp paths 可以打印实际路径。这些文件都已在 .gitignore 里,CI 也会检查它们没被提交。

环境变量

变量

用途

SSHMCP_HOME

state 目录

SSHMCP_VAULT_PASSWORD_FILE

存主密码的文件(推荐)

SSHMCP_VAULT_PASSWORD

主密码本身(会出现在进程环境里,不如用文件)

SSHMCP_HTTP_HOST / SSHMCP_HTTP_PORT

HTTP 监听地址,被同名命令行参数覆盖

主密码优先级:--vault-password-file > SSHMCP_VAULT_PASSWORD_FILE > SSHMCP_VAULT_PASSWORD > 终端提示。


凭据存储

vault.enc 是一个自包含的加密文件:

  • KDF:Argon2id,64 MiB 内存、3 轮、并行度 4,随机 16 字节 salt

  • 加密:AES-256-GCM,每次保存用新的随机 nonce

  • 完整性:整个 header(含 KDF 参数)作为 AAD 参与认证,改动工作因子会导致解密失败; 并且在派生密钥之前校验工作因子上下界,防止恶意 vault 文件用超大 memory_cost 打爆内存

  • 落盘:临时文件 + 原子替换,POSIX 上 0600

支持导入 OpenSSH、PKCS8、PKCS1、DER 格式的 Ed25519 / RSA / ECDSA 私钥。

改主密码:

sshmcp vault passwd     # 用新 salt 重新加密
sshmcp vault status     # 只看存了哪几类秘密,不显示值

主密码忘了怎么办

加密是认真的:主密码丢了,vault 里的秘密就解不出来了,没有后门。 但主机配置不在 vault 里——config.json 原样保留。

sshmcp vault reset

它会把旧文件改名为 vault.enc.bak-<时间戳>(不删除),然后用新主密码建一个空 vault, 并列出还需要重新添加密钥的主机。如果中途放弃输入新密码,会自动把原文件还原回去。

重置只影响存进去的密码和私钥;主机、端口、用户、已知主机密钥都不动。


CLI

sshmcp init [--no-vault] [--force] [--clipboard]
sshmcp host add <alias> [--hostname H] [--user U] [--port P]
                        [--identity-file PATH | --private-key PATH]
                        [--password] [--passphrase] [--sudo-password]
                        [--password-clipboard] [--passphrase-clipboard]
                        [--sudo-password-clipboard] [--secrets-stdin]
                        [--description D] [--tag T] [--host-key-policy tofu|strict]
                        [--connect-timeout S] [--command-timeout S] [--no-agent|--agent]
sshmcp host list
sshmcp host show <alias>
sshmcp host remove <alias> [--keep-secrets]
sshmcp host test <alias>
sshmcp host trust <alias> [--fingerprint SHA256:...] [--yes]
sshmcp vault status | passwd [--new-clipboard] | reset [--yes]
sshmcp token show | rotate
sshmcp audit tail [-n N]
sshmcp serve [--transport stdio|http] [--http-host H] [--http-port P]
sshmcp mcp-config [--transport stdio|http]
sshmcp paths
sshmcp doctor

全局选项 --home、--vault-password-file、--clipboard 放在子命令前后都有效。


开发

pip install -e ".[dev]"
python -m pytest -q                      # 全套
python -m pytest -m protocol -q           # 只跑真实 MCP 协议端到端

测试不依赖外部 sshd:tests/fakeserver.py 用 paramiko 的 ServerInterface + SFTPServerInterface 起一个进程内真实 SSH 服务端,走真协议,Windows 和 Linux 都能跑。 tests/test_protocol_e2e.py 更进一步,把 sshmcp 跑在子进程里,用真实 MCP 客户端走真实 JSON-RPC。

门禁里包含专门的泄露断言:命令回显密码、读含私钥的文件、异常路径、审计日志、 tool input schema、线上 payload 六处都断言无明文秘密。

设计见 PLAN.md,逐里程碑进度与踩坑记录见 PROGRESS.md。

许可

MIT,见 LICENSE。

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Enables AI assistants to securely execute remote SSH commands, perform file transfers, and monitor system status through a standardized interface. It features robust security controls including command whitelisting, blacklisting, and credential isolation to prevent unauthorized operations.
      10
      14 npm
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI assistants to securely execute commands, transfer files, and manage port forwarding on remote servers via SSH.
      158 npm
      38
      Apache 2.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI agents to manage remote servers via SSH, including command execution, multi-host batch operations, SFTP file transfer, background job handling, DevOps diagnostics, port tunneling, and safety guardrails like high-risk command blocking and read-only mode.
      MIT