SSH MCP Server
官方通过SSH从您的代理运行命令、移动文件、搜索日志和审计机器。
你可以用 SSH MCP 做什么?
- 带安全护栏运行命令 — 让您的助手通过
ssh_exec执行单条或批量命令,并具备破坏性命令保护功能,可在不可逆操作到达服务器前将其拦截。 - 读取、写入和列出远程文件 — 使用
ssh_file_read、ssh_file_write和ssh_file_list检查或修改文件,支持原子写入和可选的 SHA-256 校验。 - 搜索日志并检查服务器健康状态 — 跨文件和容器查询
ssh_log_search或ssh_log_tail,或通过ssh_snapshot和ssh_audit_baseline获取结构化健康快照。 - 带完整性检查传输文件 — 通过
ssh_upload和ssh_download上传或下载文件和目录,并自动回退到旧版scp以兼容较老设备。 - 管理长时间运行的后台任务 — 使用
ssh_exec分离慢速操作,并通过ssh_job_status、ssh_job_output和ssh_job_kill跟踪它们,即使断开连接也能继续运行。
文档
SSH MCP Server — 面向AI代理的远程服务器工具
|
|
一个SSH MCP服务器——一个多功能工具,为你和你的AI代理在调试、开发和服务器维护上节省时间和令牌。 通过SSH运行命令、移动文件、读取日志和审计机器——无论是云VPS、裸机服务器,还是你家角落里那台BusyBox路由器。 |
它使用你机器上已有的OpenSSH客户端:你的密钥、你的~/.ssh/config、你的跳板机、你的代理转发。无需捆绑任何东西,无需编译,无需原生绑定。
适用于Claude Code、Codex CLI、Cline、opencode、Gemini CLI、Qwen Code、Hermes以及其他MCP客户端。
安装 · 工具 · 设置 · 安全 · 路线图 · 文档 · 更新日志
30秒安装
无需全局安装。npx会在首次使用时下载该包:
npx -y @hypnosis/ssh-mcp-server
将其添加到你的MCP客户端——例如Claude Code——适用于每个项目:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
或者手动编写——在大多数客户端共享的配置格式中使用同一个服务器:
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@hypnosis/ssh-mcp-server"],
"env": {
"SSH_PROFILES_FILE": "~/.claude/ssh-profiles.json"
}
}
}
}
然后创建包含至少一台机器的~/.claude/ssh-profiles.json:
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
这样就足以连接了。
Codex、opencode、Qwen Code和其他客户端的说明见 设置SSH MCP服务器。
作为插件安装
某些客户端——例如Claude Code——可以将整个内容作为插件使用:
/plugin marketplace add hypnosis/ssh-mcp-server
/plugin install ssh-mcp-server@ssh-mcp-server
该插件读取~/.claude/ssh-profiles.json,除非SSH_PROFILES_FILE另有说明,因此
请先创建该文件,服务器启动时就会加载你的机器。
要求
Node.js 18+ 以及系统上的ssh客户端PATH。在Windows上,请使用基于密钥的配置文件;
目前不支持密码和口令配置文件。
偏好固定版本、离线工作,或每次启动少一次注册表检查:
npm install -g @hypnosis/ssh-mcp-server,然后使用ssh-mcp-server作为命令,而不是
npx。
适用人群
- DevOps和SRE,希望更快地进行审计、事件检查和日常服务器工作。
- 氛围编码者和独立开发者,使用AI助手发布产品,并在自己的服务器上运行所构建的内容。
- 系统管理员和平台工程师,希望使用结构化工具而不是不受限制的原始shell。
- 运行自有VPS的开发者和小团队,没有专门的运维团队。
- 家庭实验室、NAS和路由器所有者,其有用的硬件已超出现代协议的支持范围。
为什么选择SSH MCP服务器而不是原始shell
更少的令牌,更低的AI成本
原始shell给AI代理带来的是信息洪流:重复的命令、ASCII表格和日志转储。 将那些噪音转化为服务器状态图景会消耗令牌——也就是你的钱。
更快的服务器调试
专用工具批量处理常规检查,限制噪音输出,并返回关键部分。 代理花更少的时间翻译终端输出,更快地找到修复方案。
更少的猜测,更少的AI错误
结构化答案会说明发现了什么、哪些无法测量、哪些被截断。 这减少了代理用幻觉填补空白的空间——也让你更少遇到糟糕的 修复、更平静的部署和更可靠的代码。
SSH兼容性:现代服务器、旧设备和Windows
使用你现有的OpenSSH设置
没有捆绑的SSH实现,没有原生绑定,无需按平台重新构建。命令使用
系统ssh客户端,因此你的密钥、你的~/.ssh/config、你的跳板机和你的代理
转发都像在终端中一样正常工作。在支持的情况下,每个目标共享一个
多路复用连接意味着你只需认证一次,而不是每条命令一次。
对旧服务器、路由器和NAS设备的SSH支持
向一台使用现代scp的路由器发送文件,你会得到这样的结果:
scp app.conf router:/etc/
# scp: subsystem request failed on channel 0
一切正常——当前的scp使用新协议通信,而路由器并不知晓。
在终端中,你现在需要去读论坛帖子,然后带着一个额外的标志回来。在这里你
什么都不用做:传输会被尝试,拒绝会被识别,旧协议会被自动使用,
并且该机器会被记住,下次文件直接发送过去。
旧SSH客户端和缺失工具的降级方案
旧设备有降级方案,而不是死路一条。当现代功能缺失时,服务器会 在可能的情况下走旧路线:
| 你的机器 | 你会得到什么 |
|---|---|
| 一台太小而无法支持现代文件传输的路由器或NAS | 文件仍然送达——自动使用旧协议 |
| 一台十年前的服务器 | 工作流仍然有效;只是每条命令打开新连接而不是复用 |
| 一个精简镜像,无法对文件进行哈希 | 上传显示“无法验证”,而不是声称无人检查的匹配 |
| 一台未安装某工具的机器 | 答案显示“未测量”——绝不会是读作“什么都没有”的零 |
为模型上下文协议而生
基于官方MCP SDK构建,全程使用TypeScript,2500多个单元测试以及一个 针对真实容器而非模拟运行的实时测试套件。
原始SSH与SSH MCP服务器:同样的工作,两种方式
SSH服务器健康检查
场景: 刚完成一次部署。服务器感觉变慢了,你不知道是 磁盘、内存、服务、容器还是错误导致的。
问题: “这台机器健康吗?”
原始SSH
$ uptime
10:42:17 up 18 days, 3:21, 2 users, load average: 0.42, 0.31, 0.28
$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda1 ext4 40G 35G 5.0G 87% /
overlay overlay 40G 35G 5.0G 87% /var/lib/docker/overlay2/...
$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 4.9Gi 612Mi 121Mi 2.2Gi 2.5Gi
$ systemctl --failed
UNIT LOAD ACTIVE SUB DESCRIPTION
● api-worker.service loaded failed failed API background worker
$ docker ps -a
CONTAINER ID IMAGE STATUS PORTS
8e14d0b41c2a api:latest Up 3 minutes 0.0.0.0:8080->8080/tcp
65b894af2430 worker:latest Exited (1) 2 minutes ago
$ ss -tulpn
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=842,fd=3))
tcp LISTEN 0.0.0.0:8080 users:(("docker-proxy",pid=1942,fd=4))
$ journalctl -p err --since -1h | tail -50
Aug 20 10:39:14 prod api-worker[22104]: database connection timed out
Aug 20 10:39:14 prod systemd[1]: api-worker.service: Failed with result 'exit-code'.
这仍然是删减后的结果。完整的检查需要更多命令来检查CPU、服务
状态、容器数量和最近的错误,每条命令都有自己的输出格式。更糟糕的是,一台没有
ss的机器,当端口检查从未运行时,可能看起来像是零监听器。
结构化MCP结果
ssh_snapshot({ "profile": "production" })
{
"disk_pct": 87,
"mem_pct": 64,
"cpu_pct": 12,
"load": "0.42 0.31 0.28",
"containers": 7,
"ports": 14,
"services_running": 3,
"recent_errors": 21,
"unavailable": []
}
代理获得的好处
| 原始SSH | 结构化MCP | 你的收益 |
|---|---|---|
| 多条命令和ASCII表格 | 一个结果中的命名字段 | 一次调用、命名字段和更少的往返 |
| 缺失工具可能看起来像空输出 | unavailable标明未测量的内容 | 更少的猜测和更少的错误修复 |
| 你需要筛选磁盘、服务和错误 | 问题信号已经浮现 | 更快的调试 |
完整的ssh_audit_baseline结果可能比少量原始命令输出更长——
在我们的实验室测量中约为1,077个令牌对比765个。节省来自完整的
工作流,而不是让单个响应更短。
在真实的故障排查会话中,专用工具将49次单独的命令调用减少到 4次MCP调用。每次额外调用都会在累积对话的基础上开始新一轮模型推理。 提示缓存可以降低重复输入的成本,但新命令及其输出仍然消耗上下文。 更少的往返意味着整个会话中更少的令牌、更少的重复分析和更快的答案路径。
需要全貌而不是脉搏? ssh_audit_baseline批量处理系统、磁盘、
内存、端口、sshd、失败单元、Docker、防火墙和更新。结果以
CRITICAL / WARNING / OK形式呈现;未测量的部分会被标明,而不是静默读作零。
Linux服务器日志搜索
场景: API超时了,但同样的消息可能出现在nginx、syslog、 journald或你普通用户无法读取的应用程序日志中。
问题: “那个错误是从哪里来的?”
原始SSH
$ grep -i "timeout" /var/log/nginx/error.log
2026/08/20 10:38:54 [error] upstream timed out while reading response header
$ grep -i "timeout" /var/log/syslog
Aug 20 10:39:14 prod api-worker[22104]: database connection timed out
$ grep -i "timeout" /var/log/app/*.log 2>/dev/null
$ journalctl -u api --since "1 hour ago" | grep -i timeout
Aug 20 10:39:14 prod api[22104]: database connection timed out after 30000ms
第三条命令看起来干净,但2>/dev/null也隐藏了权限错误。“没有
匹配”和“没有读取”现在看起来一样。繁忙的日志也可能返回数千行,
将事件的其他部分挤出代理的上下文。
结构化MCP结果
ssh_log_search({ "profile": "production",
"path": ["/var/log/nginx/error.log", "/var/log/syslog", "/var/log/app/*.log"],
"query": "timeout", "context": 2, "since": "1h" })
{
"matches": 34,
"lines": [
{ "file": "/var/log/nginx/error.log", "line": 4821,
"text": "upstream timed out while reading response header", "context": false },
{ "file": "/var/log/nginx/error.log", "line": 4822,
"text": "client closed connection", "context": true }
],
"files_searched": 6,
"files_unreadable": ["/var/log/app/private"],
"files_skipped": 12,
"files_undated": [],
"limited": false,
"truncated": false
}
代理获得的好处
| 原始SSH | 结构化MCP | 你的收益 |
|---|---|---|
| 四次搜索和四个输出 | 一次跨文件和通配符的搜索 | 更少的令牌和往返 |
| 权限错误可能消失 | files_unreadable标明每个遗漏的路径 | 不会得出错误的“日志干净”结论 |
| 输出可能无限增长而没有有用上限 | limited和truncated暴露每个截断点 | 基于部分结果的更安全决策 |
since使用服务器的时钟,namesOnly: true只返回匹配的路径,而
ssh_log_tail在一次调用中从多个日志读取最后N行。
安全的远程配置编辑
场景: 你需要在生产服务器上替换nginx配置。连接断开、 错误的模式或未检查的复制都可能让服务留下损坏的文件。
问题: “我能否在不留下部分文件的情况下替换这个配置?”
原始SSH
$ sudo sh -c 'cat > /etc/nginx/conf.d/api.conf' <<'EOF'
server {
listen 80;
location / { proxy_pass http://127.0.0.1:8080; }
}
EOF
$ echo $?
0
退出码为零说明shell完成了。它不能证明哪些字节已落地,而且>
在新文件的第一个字节到达之前就截断了旧文件。如果连接在写入中途
断开,服务就会留下部分配置。
结构化MCP结果
ssh_file_write({ "profile": "production",
"files": [{ "path": "/etc/nginx/conf.d/api.conf",
"content": "server {\n listen 80;\n location / { proxy_pass http://127.0.0.1:8080; }\n}\n",
"mode": "644", "sudo": true, "verify": true }] })
{
"files": [{ "path": "/etc/nginx/conf.d/api.conf", "written": true,
"verified": "verified", "reason": null, "bytes": 79 }]
}
代理获得的好处
| 原始SSH | 结构化MCP | 你的收益 |
|---|---|---|
| 目标在复制完成前被截断 | 完整的临时文件通过一次重命名替换它 | 不会出现半写入的配置 |
| 只有退出码 | 字节数和验证结果被标明 | 你知道实际落地了什么 |
| 权限存在于shell文本中 | sudo、mode和verify是每个文件的字段 | 可预测的所有权和更少的引号错误 |
verified有三种诚实的结果:verified、当服务器没有哈希工具时的unavailable,
以及未请求验证时的skipped。对于读取,ssh_file_read接受
路径列表;ssh_file_list处理通配符、递归、大小和模式。
使用sudo运行批量SSH命令
场景: 部署已就绪,但在流量切换之前必须检查nginx语法、服务状态和最近的错误。一次失败的检查不应消失在合并的输出中。
问题: “每个预检检查都通过了吗?”
原始SSH
$ ssh admin@server.example.com 'sudo nginx -t'
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ ssh admin@server.example.com 'sudo systemctl is-active nginx'
active
$ ssh admin@server.example.com 'sudo tail -5 /var/log/nginx/error.log'
2026/08/20 10:38:54 [error] upstream timed out while reading response header
三次连接返回三个不相关的输出。如果命令用;连接,shell只报告最后一个退出码;如果用&&连接,后续检查在第一次失败后消失。
结构化MCP结果
ssh_exec({ "profile": "production",
"command": ["nginx -t", "systemctl is-active nginx",
"tail -5 /var/log/nginx/error.log"],
"sudo": true })
{
"commands": [
{ "command": "nginx -t", "exit_code": 0, "truncated": false, "clipped_bytes": 0,
"stdout": "", "stderr": "nginx: configuration file /etc/nginx/nginx.conf test is successful\n" },
{ "command": "systemctl is-active nginx", "exit_code": 0, "truncated": false,
"clipped_bytes": 0, "stdout": "active\n", "stderr": "" },
{ "command": "tail -5 /var/log/nginx/error.log", "exit_code": 0, "truncated": false,
"clipped_bytes": 0, "stdout": "2026/08/21 09:14:02 [error] upstream timed out\n", "stderr": "" }
],
"job_id": null
}
代理获得的好处
| 原始SSH | 结构化MCP | 你的收益 |
|---|---|---|
| 三次调用和不相关的输出 | 一个有序的命令列表 | 更少的往返 |
| 合并的shell可能隐藏中间状态 | 每条命令保留自己的exit_code | 不会遗漏失败的检查 |
sudo和引号在命令文本中重复 | sudo适用于整个批次 | 更少的引号错误 |
破坏性命令防护会在第一条命令运行前检查完整列表。如果
一个条目被拒绝,其他所有条目都会被标记为未运行,并且不会向服务器发送任何内容。
每条命令都带有自己的 stdout 和 stderr。已运行且未打印任何内容的命令,其值为空字符串;从未运行的命令则完全没有该字段,因此两者不会混淆。每条命令超过 128 KB 的输出会保留两端——开头部分用于表格,结尾部分用于日志——中间以接缝标明截断量,clipped_bytes 说明被截掉了多少。截断按字节边界进行,并回退到字符边缘,因此被裁剪的答案绝不会带有替换标记。
sudo 无需终端即可到达服务器:配置文件的应答通过标准输入交给 sudo。使用哪个机密取决于 sudoPassword(当配置文件指定了机密时)和 password(否则)——通过密钥登录的配置文件根本没有登录密码,而在机器将两者区分开来的情况下,登录密码是错误的答案。当没有可应答的内容时,回复会说明情况并指出解决途径,而不是留下 sudo 自身关于 -S 和 askpass 助手的建议。读取自身标准输入的命令绝不会被给予密码,否则密码会混入数据中。
运行长时间运行的 SSH 任务
情境: 备份或迁移的运行时间将超过代理会话。连接 可能会关闭,但你之后仍然需要其状态、输出和退出代码。
问题: “这个任务能在对话中存活下来吗?”
原始 SSH
$ ssh admin@server.example.com 'pg_dump app | gzip > /srv/backups/app.sql.gz'
client_loop: send disconnect: Broken pipe
终端已消失。你现在必须重新连接、找到进程、检查目标文件, 并猜测备份是完成了还是中途停止。
结构化 MCP 结果
ssh_exec({ "profile": "production",
"command": "pg_dump app | gzip > /srv/backups/app.sql.gz",
"detach": true })
{
"commands": [{
"command": "pg_dump app | gzip > /srv/backups/app.sql.gz",
"exit_code": null,
"truncated": false,
"timed_out": false,
"blocked": false,
"blocked_reason": null,
"not_run": false,
"warning": null
}],
"job_id": "mst0f2q1-9ab3c4d5"
}
代理获得的好处
| 原始 SSH | 结构化 MCP | 你的收益 |
|---|---|---|
| 任务绑定在单个 SSH 会话上 | 远程任务具有持久 ID | 安全断开和重启 |
| 重新连接意味着搜索进程和文件 | 状态和退出代码具有命名状态 | 无需猜测是否完成 |
| 再次读取输出会重复旧文本 | 输出从字节偏移处继续 | 长时间任务降低令牌使用量 |
任务状态保存在远程磁盘上,而非此服务器的内存中。ssh_job_status 区分
running、finished 和 lost;ssh_job_output 从最后一个字节偏移处继续;而
ssh_job_kill 向整个进程组发送信号,而不仅仅是其 shell。
向旧式路由器和 NAS 设备传输文件
情境: 当前的 OpenSSH 客户端尝试 SFTP,但路由器或 NAS 只理解 经典的 scp 协议。文件仍必须完整到达并安全替换其目标。
问题: “这台旧设备还能接收经过验证的文件吗?”
原始 SSH
$ scp app.conf operator@router:/etc/app.conf
subsystem request failed on channel 0
scp: Connection closed
通常的下一步是记住旧式标志、重试复制,然后运行单独的 哈希命令——如果设备有哈希工具的话。
结构化 MCP 结果
ssh_upload({ "profile": "router", "local_path": "./app.conf",
"remote_path": "/etc/app.conf", "sudo": true,
"mode": "644", "owner": "root:root", "verify": true })
{
"files": [{
"path": "/etc/app.conf",
"written": true,
"verified": "verified",
"reason": null,
"bytes": 1284
}]
}
代理获得的好处
| 原始 SSH | 结构化 MCP | 你的收益 |
|---|---|---|
| 现代 SFTP 模式在第一个错误处停止 | 经典 scp 回退是自动且被记住的 | 旧设备仍可工作 |
| 成功复制不能证明完整性 | SHA-256 验证具有命名结果 | 损坏不会被误认为成功 |
| 直接替换可能留下部分目标 | 临时文件在传输后移动到目标位置 | 工作文件在中断中幸存 |
如果设备既没有 sha256sum 也没有 openssl,结果会显示 unavailable 并说明
原因,而不是报告虚假匹配。整个目录使用 recursive: true,并
一次性批量验证其哈希。
面向 AI 代理的破坏性命令保护
防护在本地运行,在命令到达 SSH 之前。它将可恢复的操作与 破坏保存数据容器的操作区分开来,并检查链和批次中的命令顺序。
在破坏性链开始前阻止它
一个安全的备份和替换序列:
cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old && rm -rf /srv/app
相同操作但顺序错误:
rm -rf /srv/app && cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old
# REFUSED before the first command runs
Shell 会删除目录,然后才发现备份源已消失。
防护看到后续步骤读取的目标已被更早的步骤破坏,因此
整个调用停留在你的机器上。同样的检查也会捕获
dropdb app && pg_dump app > backup.sql。
拒绝不可逆的丢失,警告可恢复的更改
| 拒绝——容器本身 | 仅警告——其内容 |
|---|---|
DROP DATABASE、dropdb | DROP TABLE、TRUNCATE、DELETE FROM |
docker volume rm、docker compose down -v | docker rm -f <name> |
crontab -r | 编辑一个任务 |
mkfs、wipefs -a、lvremove、zfs destroy | chmod 777 |
reboot、shutdown、halt | git reset --hard |
docker compose down -v 被拒绝,因为 -v 会移除命名的 Docker 卷,包括数据库
卷。没有 -v,停止服务不被视为相同的不可逆操作。
递归删除文件系统根目录、主目录或系统树(如 /etc、
/var 和 /usr)也会被拒绝,包括符号链接指向这些位置的情况。未解析的目标
(如 rm -rf "$DIR"/*)也会被拒绝:“无法检查”不被视为“安全”。
指明你要停止的对象
找到目标而非指明目标的命令不会被发送。服务器会展开它, 并回答目标背后是什么:
docker kill $(docker ps -q --filter ancestor=web)
# BLOCKED — would stop:
# edge — web:latest, Up 34 days, 0.0.0.0:8443->8443/tcp
对于进程,答案会添加表明其正在使用的迹象:已运行多长时间、
接受连接的端口、正在承载的连接数。命名目标
不增加额外成本,且静默通过——docker kill web-1、kill 4871、
systemctl stop app。
要继续,请指明要停止的对象。名称会与命令实际 到达的目标进行核对,因此已漂移到其他对象的掩码会被拒绝而非 确认:
docker kill $(docker ps -q --filter ancestor=web) # CONFIRMED-KILL: edge
针对命令行模式的模式是另一种情况。它匹配携带它的命令本身, 因此运行它的 shell 会在目标之前收到信号,回复会在 中间中断。这种打击不会被确认,而是被重写——按编号,或 将一个字符写成类,使模式不再匹配自身:
pkill -f relay
# BLOCKED — two ways through:
# kill 4871
# pkill -f '[r]elay' # CONFIRMED-KILL: 4871
三种结果保持区分:找到目标、展开未到达任何内容、以及 没有可询问的内容——机器上没有引擎、答案被截断、连接失败。后两种 也是拒绝:不知道不是继续的理由。
确认有意的破坏性命令
没有什么是永久禁止的。在已审查的命令中添加 # CONFIRMED-DESTRUCTIVE,它
就会被允许通过。当防护拒绝批次中的一个条目时,整个批次会在
执行前停止,因此服务器绝不会处于半运行操作之后的状态。
防护在单个调用内工作。它无法将一个调用中的删除与 下一个调用中的读取连接起来,也无法推理它不认识的工具。它是安全带,而非策略 引擎:可恢复的操作仍由你决定。路径限制和引号规则 记录在 docs/security.md 中。
工具
18 个用于服务器操作的 SSH MCP 工具。完整参数和示例见 docs/tools.md。
| 工具 | 功能 |
|---|---|
ssh_exec | 运行一条命令或一批命令,带破坏性命令防护和可选分离 |
ssh_file_read | 读取一个或多个文件,文本或二进制 |
ssh_file_write | 以原子重命名和可选 SHA-256 验证写入文件 |
ssh_file_list | 列出目录,带可选通配符和递归 |
ssh_upload | 通过 SSH 上传文件或目录,二进制安全且带完整性检查;目录替换目标或合并到其中 |
ssh_download | 通过 SSH 下载文件或目录,二进制安全且带完整性检查 |
ssh_job_status | 后台任务状态:运行中、已完成或丢失 |
ssh_job_output | 从字节偏移处读取累积输出 |
ssh_job_list | 列出任务,清除超过 TTL 的已完成任务 |
ssh_job_kill | 向任务的整个进程组发送信号 |
ssh_log_tail | 一个或多个日志的最后 N 行,支持通配符;按名称指定容器 |
ssh_log_search | 跨日志或通过容器日志进行模式搜索 |
ssh_snapshot | 一次性健康快照:服务、资源、Docker、网络、错误 |
ssh_monitor | 传输控制:统计、重载、测试、列表、关闭 |
ssh_audit_baseline | 系统、磁盘、内存、网络、SSH、服务、Docker、防火墙、更新 |
ssh_tls_check | 域名的证书过期、SAN、链和续期钩子 |
ssh_disk_breakdown | 磁盘去向:du 前 N 项、Docker、journald、缓存 |
ssh_service_status | systemctl status 加上单个单元的 journalctl 尾部 |
MCP 工具安全注解
标准 MCP 注解告知客户端哪些工具是只读、破坏性、幂等或 开放世界的。参见 完整表格。
运行 SSH 命令并管理远程文件
命令、文件读写、目录列表——机器上的日常操作,每个 答案都已解析。
监控长时间运行的 SSH 任务
慢速工作被分离并跟踪,而非等待:每次查看都说它进展到何处。
搜索日志并检查服务器健康
文件和容器的日志,以及机器的一次性快照,输出有上限, 因此尾部不会吞噬上下文窗口。
通过 SSH 上传和下载文件
带完整性检查的二进制安全传输。详情见 docs/transfer.md。
对于二进制和大文件,请使用
ssh_upload/ssh_download— base64 块和 heredoc 不是二进制安全或原子的。
通过 SSH 审计 Linux 服务器
只读且批处理为一次往返。详情见 docs/audit.md。
Windows SSH 兼容模式
Windows 自动使用兼容模式。当连接多路复用 不可用时,服务器切换到每条命令一个连接。相同的工具 仍可通过基于密钥的 SSH 使用——无需单独设置或 Windows 特定实现。
破坏性命令防护在 面向 AI 代理的破坏性命令保护 中介绍。
设置 SSH MCP 服务器
首先从 30 秒安装 运行包,然后创建 配置文件。
创建 SSH 连接配置文件
把它放在你喜欢的地方——通常放在代理自身配置旁边。以下示例使用 ~/.claude/ssh-profiles.json;对于其他代理,请替换目录(~/.codex/、~/.qwen/、~/.config/opencode/):
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
明确选择 SSH 配置文件
服务器没有回退的配置文件:每个配置文件对应不同的机器,而 发送到错误机器的命令不是错误消息事后能撤销的。不带名称询问, 答案会列出可供选择的名称:
ssh_exec({ command: "uptime" })
→ No profile specified. Name one explicitly: production
服务器无法用于 SSH 的配置文件——没有 host、没有 username 或 mode: "local"——会被静默跳过,且它不认识的字段会被保留,因此该文件可以与其他工具共享。具有损坏字段的配置文件是另一种情况:它会连同字段和值一起被指出,而其健康的邻居继续工作。
每个配置文件可选地接受一个 pathSecurity 块,用于白名单或黑名单文件工具可能触及的路径——参见 docs/security.md。
通过密钥登录但远端需要 sudo 的配置文件接受一个 sudoPassword——用于应答的机密 sudo,在许多机器上这不是登录密码。将其保存在机密文件中,而不是此处。
将 SSH 密码和口令短语排除在配置文件之外
优先使用密钥。如果确实无法避免使用密码或加密密钥的密码短语,请将其保存在单独的 secrets 文件中,切勿直接放在配置文件本身中:
{
"secretsFile": "~/.config/ssh-mcp/secrets.json",
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin"
}
}
}
secrets 文件以配置文件名为键——参见 secrets.json.example:
{
"production": { "password": "..." },
"buildbox": { "sudoPassword": "..." }
}
sudoPassword 是该机器上对 sudo 的应答。使用密钥登录的配置文件没有登录密码可提供,
当两者不同时,登录密码是错误的答案;没有它,将使用 password。
secrets 文件必须仅你可读(chmod 600)。相对路径从配置文件解析;secrets 不进入
argv,并在日志中被掩码。参见
凭据安全。
配置 Claude Code、Codex 及其他 MCP 客户端
选择你使用的客户端,并将其指向同一个配置文件。
Claude Code
一条命令;-s user 使服务器在每个项目中可用:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Codex CLI
codex mcp add ssh \
--env SSH_PROFILES_FILE="$HOME/.codex/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
opencode
将其放入 ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"ssh": {
"type": "local",
"command": ["npx", "-y", "@hypnosis/ssh-mcp-server"],
"enabled": true,
"environment": {
"SSH_PROFILES_FILE": "~/.config/opencode/ssh-profiles.json"
}
}
}
}
Qwen Code
一条命令,与其他相同:
qwen mcp add ssh \
-e SSH_PROFILES_FILE="$HOME/.qwen/ssh-profiles.json" \
npx -y @hypnosis/ssh-mcp-server
其他 MCP 客户端
Gemini CLI、Hermes、Cline、编辑器插件或你自己的代理都以相同方式工作。它们 只需要一个要运行的命令和一个环境变量。
重启你的 MCP 客户端
重启客户端,然后运行 ssh_monitor({ action: "list" }) 以确认配置文件已加载。
SSH MCP 服务器配置
| 变量 | 作用 | 默认值 |
|---|---|---|
SSH_PROFILES_FILE | 配置文件 JSON 的路径——必需 | — |
SSH_MCP_LOG_LEVEL | debug、info、warn、error | info |
LOG_LEVEL | 回退,仅在 SSH_MCP_LOG_LEVEL 未设置时使用 | info |
SSH_MCP_LOG_TIMESTAMP | 日志行中的时间戳 | true |
SSH_MCP_CONTROL_PERSIST | 共享连接在最后一条命令后保持存活的秒数;0 立即关闭它 | 600 |
SSH_MCP_CONTROL_DIR | 控制套接字的存放位置 | ~/.ssh/ssh-mcp |
SSH_MCP_PROFILES_CACHE_TTL | 配置文件缓存 TTL,毫秒 | 60000 |
SSH_MCP_PROFILES_WATCH | 配置文件更改时重新加载 | true |
共享连接有意比此进程存活更久:退出时关闭它会切断同一机器上另一个窗口正在使用的通道。
SSH MCP 服务器限制
每个限制都告诉你绕过它的方法。 无法执行某操作的工具会说明原因并指出
ssh_exec,它直接在机器上运行命令——不支持的日志驱动程序、
机器上没有的实用程序、此服务器不支持的引擎。你无需提前知道工具的边界:
拒绝会在关键时刻说明原因。
三种拒绝有意对 shell 保持沉默,因为在那里它是错误的答案:你的配置文件禁止的路径
(绕过自己的规则不是修复)、格式错误的调用(修复在调用本身中),以及来自
ssh_exec 本身的拒绝。
- 取消: 已取消的调用现在也会停止服务器上的命令,作为同一连接上的第二次调用发送。在服务器没有
/proc的情况下,命令通过ps找到。FreeBSD 未经验证:不保证其行为正确。文件传输和ssh_snapshot完全不接受取消。 - 原子写入: BSD 和 macOS 无法预先检查跨文件系统的重命名。
SSH MCP 服务器路线图
-
针对 macOS SSH 主机的完整测试运行
-
在 Windows 上的端到端兼容性运行
-
多主机审计——在一次调用中比较多个 SSH 配置文件的健康状况
-
从现有
~/.ssh/config导入配置文件 -
大文件和不稳定连接的可恢复传输
-
远程操作时间线——命令、传输和守卫决策在一个审计跟踪中
-
现成的 SSH 故障排除手册
-
不降级到 shell 的容器日志— 已完成:ssh_log_tail和ssh_log_search接受容器名称,询问 docker 日志写入位置,并使用与其他日志相同的机制读取该文件 -
让你卡住的拒绝— 已完成: 每个限制现在都指出ssh_exec作为通过方式,因此触及工具边界只需一句话,而不是猜谜游戏 -
到达模型的答案— 已完成: 命令输出、匹配的日志行、机器名称和快照部分在字段中传输,而不仅仅在文本中 -
更小的 MCP 工具模式— 已完成: 工具列表减轻了 10%,分离的任务现在显示其写入的最后几行,而不是盲目轮询 -
root 下的长时间工作— 已完成: 分离的任务以sudo运行并作为 root 跟踪,仅密钥配置文件用其自身的sudoPassword应答sudo
开发和测试 SSH MCP 服务器
npm install
npm run build # tsc
npx tsc --noEmit # types, plus dead declarations
npm run test:unit # unit tests
npm run lab:up # start the two test containers
npm run test:live # live suite against those containers
实时套件针对真实容器运行——一个 BusyBox、一个 coreutils——因为两者悄悄分歧,而模拟与编写者一致。参见 docs/architecture.md 了解布局。
喜欢 SSH MCP Server?⭐
如果你喜欢这个工具,在 GitHub 上给它一个星标——这有助于更多人发现该项目。
为 SSH MCP 服务器做贡献
欢迎在 github.com/hypnosis/ssh-mcp-server 提交问题和拉取请求。
许可证
MIT——参见 LICENSE。