mcpField
M
MCP SSH Gateway

MCP SSH Gateway

本地服务
github.com云与基础设施34 周前更新

关于 MCP SSH Gateway

MCP SSH Gateway 为 AI Agent 提供可保持状态的 SSH shell,可在命令结束时返回退出码。它面向需要远程执行命令和操作文件的 Agent,也支持 Keenetic 路由器 CLI。

主要功能

  1. 01运行并跟踪远程命令
  2. 02跨调用保持 shell 状态
  3. 03读取分段输出与历史内容
  4. 04处理交互输入和中断
  5. 05通过 SFTP 或 shell 操作文件

适用场景

  1. 01检查远程主机磁盘用量
  2. 02在持续会话中运行构建命令
  3. 03管理 Keenetic 路由器
  4. 04读取或编辑远程文件

MCP 相关

它通过 stdio 提供 MCP 工具,用于列出服务器、运行和读取命令、管理 shell 会话及操作远程文件。可从 SSH 配置或服务器配置文件载入主机。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"d00mus-mcp-ssh-gateway": {
"command": "uvx",
"args": [
"mcp-ssh-gateway"
],
"env": {
"SSH_SERVERS_CONFIG": "<SSH_SERVERS_CONFIG>",
"SSH_MCP_CACHE_DIR": "<SSH_MCP_CACHE_DIR>",
"SSH_READ_ONLY": "<SSH_READ_ONLY>",
"SSH_COMMAND_BLACKLIST": "<SSH_COMMAND_BLACKLIST>"
}
}
}
}
启动命令uvx mcp-ssh-gateway
需要配置的环境变量
SSH_SERVERS_CONFIGPath to the JSON file with SSH server targets, or an inline JSON string. Overridden by --servers-config.
SSH_MCP_CACHE_DIRWhere logs and known_hosts are kept. Default: the user cache directory.
SSH_READ_ONLYSet to true/1 to force the global read-only guardrail.
SSH_COMMAND_BLACKLISTComma-separated global prohibited command patterns.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypimcp-ssh-gateway7.0.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

MCP SSH Gateway

为能判断命令何时完成的 AI Agent 提供 SSH 终端。

六种工具、能保留状态的真实 Shell、命令结束时立即返回的退出代码,以及小型模型也能据此采取行动的答复。仅有一个直接依赖项(paramiko),无需守护进程,也无需云服务。

CI Python 3.11+ PyPI License: MIT

English · Русский

为什么选择它

  • 它知道命令何时完成。 Shell 会直接报告命令结束及其退出代码,因此 echo hi 几毫秒内就会返回,失败的命令会显示 exit_code: 2,而耗时三分钟的构建会在 10 秒后返回 running,之后可通过 read 获取结果。无需等待,也无需靠沉默猜测。
  • 保留状态的终端。 一个会话就是一个真实的 Shell:cd、变量和虚拟环境会在多次调用间保留。不带 session_id 的 run 会打开新的 Shell,因此不会意外共享任何内容。
  • 专为小型模型打造。 六种工具,描述总计约 1.6k 个 token。每次停止都会说明下一步该做什么(hint);分页器已关闭,进度条只占一行,参数错误时会给出正确调用方式,长输出则会分页显示(has_more、tail、offset)。
  • 提问不会卡住。 Continue? [y/N]、密码提示或未闭合的引号都会返回 waiting_input。Agent 可通过 signal 作答或按下 Ctrl+C,Shell 会保留状态。
  • 也支持路由器。 可通过同一组工具操作 Keenetic 路由器 CLI(支持处理 show interface 和 --More--),或其 Linux Shell。
  • 已通过真实 sshd 测试。 CI 会在每次推送时运行测试,涵盖使用 bash、BusyBox ash、zsh、dash、fish 和 tcsh 登录的容器,以及没有 SFTP 的主机。

快速开始

你需要 uv(或 Python 3.11+ 和 pip),以及对你所控制主机的 SSH 访问权限。

已经有 ~/.ssh/config? 将以下内容添加到 MCP 客户端,该文件中的每台主机都会成为一台服务器(通过密钥或 SSH Agent 登录):

{
  "mcpServers": {
    "ssh": {
      "command": "uvx",
      "args": ["mcp-ssh-gateway", "--import-ssh-config"]
    }
  }
}

这是 Cursor(~/.cursor/mcp.json)和 Claude Desktop(claude_desktop_config.json)所用的格式;其他客户端见下文。重启客户端并询问:“列出我的 SSH 服务器,并显示 web 上的磁盘使用情况。”

想使用密码登录、路由器或只读主机? 编写一个 servers.json:

{
  "servers": {
    "web": {"host": "192.168.1.10", "user": "deploy", "key_path": "~/.ssh/id_ed25519"},
    "router": {"host": "192.168.1.1", "user": "admin", "password": "${ROUTER_PASSWORD}"}
  }
}

然后通过 "args": ["mcp-ssh-gateway", "--servers-config", "/absolute/path/to/servers.json"] 将该文件路径传给网关。请使用绝对路径(客户端不会从你的文件夹启动;在 Windows 上,要在 JSON 中转义反斜杠),并在客户端的 "env" 块中提供该文件引用的变量。servers.json.example 还展示了只读生产主机的配置。如果不使用 uv:运行 pip install mcp-ssh-gateway,并设置 "command": "mcp-ssh-gateway"。

其他客户端

Claude Code

claude mcp add ssh --scope user -- uvx mcp-ssh-gateway --import-ssh-config

VS Code(.vscode/mcp.json:顶层键为 servers)

{
  "servers": {
    "ssh": {"type": "stdio", "command": "uvx", "args": ["mcp-ssh-gateway", "--import-ssh-config"]}
  }
}

Codex(~/.codex/config.toml,或运行 codex mcp add ssh -- uvx mcp-ssh-gateway --import-ssh-config)

[mcp_servers.ssh]
command = "uvx"
args = ["mcp-ssh-gateway", "--import-ssh-config"]
startup_timeout_sec = 30   # the first uvx run downloads the package

此网关也已列入官方 MCP Registry,名称为 io.github.d00mus/mcp-ssh-gateway,供读取该注册表的客户端和目录使用。

Agent 看到的内容

客户端收到的回答,来自测试套件的 Debian 容器(主机地址和磁盘数据已替换为示例值):

server_list()
→ {"servers":[{"server":"web","host":"192.168.1.10:22","user":"deploy"}]}

run(server="web", command="df -h /")                  # no session_id: a new shell is opened
→ {"session_id":"web/1","status":"completed","exit_code":0,
   "output":"$ df -h /\nFilesystem      Size  Used Avail Use% Mounted on\n/dev/vda1        40G   31G  7.2G  82% /\n"}

run(session_id="web/1", command="ls /nonexistent")    # the same shell; a non-zero code is a result
→ {"session_id":"web/1","status":"completed","exit_code":2,
   "output":"$ ls /nonexistent\nls: cannot access '/nonexistent': No such file or directory\n"}

run(session_id="web/1", command="read -p 'Continue? [y/N] ' a; echo got:$a")
→ {"session_id":"web/1","status":"waiting_input",
   "output":"$ read -p 'Continue? [y/N] ' a; echo got:$a\nContinue? [y/N] ",
   "hint":"The program waits for input. Answer with signal action=stdin text=..., or stop it with signal ctrl_c."}

signal(session_id="web/1", action="stdin", text="y")
→ {"session_id":"web/1","status":"completed","output":"got:y\n","exit_code":0}

run(session_id="web/1", command="seq 1 500", lines=5)  # long output comes in pages
→ {"session_id":"web/1","status":"completed","output":"$ seq 1 500\n1\n2\n3\n4\n","exit_code":0,"has_more":496}

read(session_id="web/1", tail=3)                       # just the end
→ {"session_id":"web/1","status":"completed","output":"498\n499\n500\n","exit_code":0,"skipped_lines":493}

read(session_id="web/1", offset=-20, lines=3)          # scroll back; the unread position stays put
→ {"session_id":"web/1","status":"completed","output":"481\n482\n483\n","exit_code":0}

run(session_id="web/1", command="sleep 30", wait=1)
→ {"session_id":"web/1","status":"running","output":"$ sleep 30\n",
   "hint":"Still running. Call read(session_id='web/1') again, or stop it with signal ctrl_c."}

signal(session_id="web/1", action="ctrl_c")
→ {"session_id":"web/1","status":"interrupted","output":"\n","process_stopped":true}

出错时,回答会指出正确的调用方式。下面是模型为 run 臆造了 cwd 参数时读到的内容:

{"error": "run has no argument 'cwd'. It takes: command, server, session_id, shell, wait, timeout, lines. To work in a folder, start the command with 'cd /path && '."}
字段含义
statuscompleted、running、waiting_input、interrupted、timed_out、failed 或 idle。
exit_code仅在 completed 时出现。非零值是结果,不是工具错误。
has_more会话中尚未读取的行;read 会返回这些行。
hint在 Agent 否则需要猜测时,提示下一步该做什么。
skipped_lines较新的命令或 read(tail=…) 越过的较早未读行。内容不会丢失:read(offset=0) 可回滚查看。
dropped_data已永久丢失的未读输出:会话缓冲区溢出(保留最近 400 万个字符)。

工具

工具功能
server_list列出服务器及其打开的会话。
run运行命令。命令结束时返回;或在经过 wait 秒(默认 10 秒)后返回 running。未提供 session_id 时,会打开一个新的 shell。
read读取会话中接下来的未读行(运行中的命令最多等待 wait 秒);tail 用于读取末尾内容,offset 用于回滚查看。
signalstdin 用于回答问题,ctrl_c 用于停止命令,ctrl_d 用于结束输入。
session_close关闭一个 shell。每台服务器只允许打开少量 shell(max_sessions,默认值为 8)。
file通过 SFTP 或在主机不支持 SFTP 时通过 shell,对远程文件执行 list、read、write 和 edit。读取可进行筛选(contains、tail_lines);编辑是精确文本替换,可通过 expected_sha256 进行校验。
server_add仅在使用 --allow-add-server 时可用:将服务器添加到 servers.json。

会话的名称格式为 server/N。Shell 会保留状态(目录、变量),因此没有默认 shell:不带 session_id 的 run 总会打开一个新的 shell 并返回其 ID,之后该 ID 会继续指向同一个 shell。用完后请关闭 shell。

支持的环境

主机状态
使用 bash、dash、BusyBox ash 或 zsh 作为登录 shell 的 Linux可用。已在真实的 sshd 容器(Debian、Alpine、zsh 和 dash 登录环境)中测试。
使用 fish 或 tcsh 作为登录 shell 的主机在 servers.json 中设置 "shell": "bash" 即可使用(网关会在登录后运行 exec bash)。否则网关会立即拒绝并说明原因。
Keenetic 路由器(NDM CLI 及其背后的 Linux shell)可用。测试中使用了脚本模拟环境,也已在真实路由器上日常使用。
其他路由器 CLI(Cisco IOS、Junos 等)尚不支持: #1。普通厂商 CLI 可能可用,但尚未测试。
位于堡垒机后面的主机(ProxyJump)尚不支持: #2。--import-ssh-config 会忽略 ProxyJump。
SSH shell 为 cmd 或 PowerShell 的 Windows 主机不支持:网关需要 POSIX shell。

网关本身可运行在支持 Python 3.11+ 的任何环境中(Linux、macOS、Windows)。

安全

Agent 拥有你提供的 SSH 账户所具备的权限。以下措施可以缩小错误操作造成的损害,但不能替代受限账户。

  • 防护措施用于防止误操作。 read_only 和 command_blacklist(按字面匹配,不区分大小写)会检查命令文本。Shell 技巧、base64 或 python -c 都能绕过它们。要建立真正的边界,请使用受限 SSH 账户:只读 Shell、ForceCommand,并为不同信任级别设置不同凭据。
  • 无法访问本地文件。 只有在使用 --project-root <folder> 启动网关后,local_path(上传、下载)才可用,而且只能访问该文件夹内的内容。
  • 主机密钥: 首次连接时会信任新主机,并将其密钥保存在缓存目录中的 known_hosts;如果密钥发生变化,则会拒绝连接。verify_host: false 会关闭检查。
  • 密钥: 在 servers.json 中使用 ${NAME} 引用,而不是直接填写密钥值。日志会隐藏类似密码的值(--log-output off 不会写入任何日志)。
  • 不开放监听端口: 网关仅通过 stdio 使用 MCP 通信。除非使用 --allow-add-server 启动,否则 server_add 不可用;且此功能只会追加内容。

完整政策及问题报告方式:SECURITY.md。

配置

servers.json 字段:host、user、port(22)、key_path(支持 ~ 和 ${NAME})、password 和 key_passphrase(仅替换 ${NAME} 引用,其余内容按原样使用)、verify_host(true)、description、extra_path(添加到 PATH)、read_only、command_blacklist、max_sessions(8)、shell(登录后要执行的 POSIX Shell,适用于 fish 或 tcsh 账户)。网关运行期间会重新读取该文件;未更改的服务器会保留其会话。

命令行(除了服务器来源外,均为可选):

选项含义
--servers-configservers.json 的路径,或 JSON 内容本身。也可使用 $SSH_SERVERS_CONFIG;默认回退到 ./servers.json。
--import-ssh-config同时提供 ~/.ssh/config 中的主机(使用密钥或 Agent 登录)。
--read-only、--command-blacklist a,b对所有服务器启用防护措施(或使用 $SSH_READ_ONLY、$SSH_COMMAND_BLACKLIST)。
--log-output meta|full|off写入日志文件的内容(默认值为 meta:命令和生命周期事件)。
--cache-dir日志和 known_hosts 的存放目录。默认:用户缓存目录($SSH_MCP_CACHE_DIR)。
--project-rootfile 工具可读写的本地文件夹(local_path)。不设置时无法访问本地文件;该工具仍可用于服务器。
--allow-add-server、--allow-system-temp、--allow-gateway-dir可选启用的额外功能。

日志:每个会话和每条命令在缓存目录下分别生成一个 JSON Lines 文件;旧文件会按时间和大小清理。

工作原理

网关会通过 PTY 打开一个交互式 Shell,并用一行初始化命令将其切换到安静、机器可读的模式:关闭回显,并设置一个会打印包含退出代码标记的提示符。标记出现时,命令即执行完毕,Agent 也会获得其退出代码。这同样适用于语法错误、后台任务和 Ctrl+C。未闭合的引号或 heredoc 会通过第二个标记识别。

登录后使用供应商 CLI 的路由器(Keenetic NDM)没有这样的 Shell。对于这类路由器,网关会从登录横幅中学习提示符,移除输入命令的回显,在 --More-- 处按空格,并将稳定的提示符视为命令结束。run(shell=false) 使用 CLI,run(shell=true) 则进入其背后的 Linux Shell;一个会话只会保持一种模式。

输出会写入每个会话各自的逐行缓冲区,并使用单一的未读位置。run 和 read 会推进该位置;tail 会跳到末尾;offset 只查看内容。超过 1024 个字符的行会被拆分,以确保分页结果可预测。

限制

  • 一个 shell 一次只运行一条命令。要同时处理两件事,请打开两个 shell(或让客户端并行调用 run)。
  • 在会话内启动的 shell(sudo -s、su、bash)会隐藏命令结束的位置,因此网关会告知 Agent 退出该 shell。需要 root 权限时,请使用 sudo <command>。
  • 暂不支持跳板机(#2),也暂不支持其他路由器 CLI(#1)。
  • 网关没有审批、策略引擎或审计跟踪。如果需要这些功能,请参阅下文。

其他用于 MCP 的 SSH 服务

它们的取舍各不相同(截至 2026 年 9 月;选择前请阅读它们的 README):

  • tufantunc/ssh-mcp:安全优先。提供命令分类、角色和主机策略、人工审批、审计日志、ProxyJump、SSH CA 和流式 SFTP;共 14 个工具。如果你需要审批和审计跟踪,适合用于生产环境中的服务器群。
  • classfang/ssh-mcp-server:基于 Node(npx)提供 4 个工具,具有命令白名单、SOCKS 和 HTTP 代理、堡垒机模式及 MFA。如果你需要代理或 MFA,且想要最精简的 Node 配置,就选它。
  • bvisible/mcp-ssh-manager:提供 37 个工具,是一套包含备份、数据库和监控功能的 DevOps 工具箱。如果你想直接使用这些现成的工作流,就选它。

这个网关选择了另一端:工具数量少、shell 有状态,并且能为 Agent 遇到的每种卡住情况提供解决方案。

Docker

docker build -t mcp-ssh-gateway .
docker run -i --rm \
  -v /absolute/path/to/servers.json:/app/servers.json:ro \
  -v /absolute/path/to/.ssh:/root/.ssh:ro \
  -v /absolute/path/to/cache:/app/.ssh-cache \
  mcp-ssh-gateway --servers-config /app/servers.json --cache-dir /app/.ssh-cache

缓存卷会在多次运行之间保留 known_hosts 和日志。

开发

pip install -r requirements.txt ruff mypy
python -m unittest discover -s tests -t .     # unit tests; integration tests need Docker
ruff check . && mypy

集成测试会启动真实的 sshd 容器,并通过客户端使用的同一 JSON-RPC 入口驱动网关。缺少 Docker 时,测试会自动跳过(设置 MCP_SSH_IT=0 可主动跳过)。请参阅 CONTRIBUTING.md 和变更日志。采用 MIT 许可证。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。