SSH MCP Server

官方

通过SSH从您的代理运行命令、移动文件、搜索日志和审计机器。

你可以用 SSH MCP 做什么?

  • 带安全护栏运行命令 — 让您的助手通过 ssh_exec 执行单条或批量命令,并具备破坏性命令保护功能,可在不可逆操作到达服务器前将其拦截。
  • 读取、写入和列出远程文件 — 使用 ssh_file_readssh_file_writessh_file_list 检查或修改文件,支持原子写入和可选的 SHA-256 校验。
  • 搜索日志并检查服务器健康状态 — 跨文件和容器查询 ssh_log_searchssh_log_tail,或通过 ssh_snapshotssh_audit_baseline 获取结构化健康快照。
  • 带完整性检查传输文件 — 通过 ssh_uploadssh_download 上传或下载文件和目录,并自动回退到旧版 scp 以兼容较老设备。
  • 管理长时间运行的后台任务 — 使用 ssh_exec 分离慢速操作,并通过 ssh_job_statusssh_job_outputssh_job_kill 跟踪它们,即使断开连接也能继续运行。

文档

SSH MCP Server — 面向AI代理的远程服务器工具

SSH MCP Server

一个SSH MCP服务器——一个多功能工具,为你和你的AI代理在调试、开发和服务器维护上节省时间和令牌。

通过SSH运行命令、移动文件、读取日志和审计机器——无论是云VPS、裸机服务器,还是你家角落里那台BusyBox路由器。

它使用你机器上已有的OpenSSH客户端:你的密钥、你的~/.ssh/config、你的跳板机、你的代理转发。无需捆绑任何东西,无需编译,无需原生绑定。

适用于Claude Code、Codex CLI、Cline、opencode、Gemini CLI、Qwen Code、Hermes以及其他MCP客户端。

MCP Registry Glama Smithery npm downloads tests

安装 · 工具 · 设置 · 安全 · 路线图 · 文档 · 更新日志


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另有说明,因此 请先创建该文件,服务器启动时就会加载你的机器。

要求

npm version Node.js TypeScript MCP SDK

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标明每个遗漏的路径不会得出错误的“日志干净”结论
输出可能无限增长而没有有用上限limitedtruncated暴露每个截断点基于部分结果的更安全决策

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文本中sudomodeverify是每个文件的字段可预测的所有权和更少的引号错误

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适用于整个批次更少的引号错误

破坏性命令防护会在第一条命令运行前检查完整列表。如果 一个条目被拒绝,其他所有条目都会被标记为未运行,并且不会向服务器发送任何内容。 每条命令都带有自己的 stdoutstderr。已运行且未打印任何内容的命令,其值为空字符串;从未运行的命令则完全没有该字段,因此两者不会混淆。每条命令超过 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 区分 runningfinishedlostssh_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 DATABASEdropdbDROP TABLETRUNCATEDELETE FROM
docker volume rmdocker compose down -vdocker rm -f <name>
crontab -r编辑一个任务
mkfswipefs -alvremovezfs destroychmod 777
rebootshutdownhaltgit 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-1kill 4871systemctl 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_statussystemctl 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、没有 usernamemode: "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_LEVELdebuginfowarnerrorinfo
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_tailssh_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