mcp-ssh-go:8 个 SSH 工具、一个静态 Go 二进制,输出上限按模型上下文窗口算
项目信息
- 上游仓库(规范):enkiEng/mcp-ssh-go
- 本文引用镜像:agczsz/mcp-ssh-go(fork 自 NRC-Research/mcp-ssh-go)
- 许可:Apache-2.0
定位:给 AI agent 一组刻意收窄、可审计的 SSH 能力,通过带鉴权的 Streamable HTTP 暴露。多数 SSH MCP server 会把能开的都开出来 —— 交互式 PTY、sudo/su、端口转发、几十个工具 —— 那是一个又大又难审计的权限面。这个项目反着做:只有八个离散、可记录的操作,别的没有。没有交互终端,没有提权,没有隧道,没有 shell 逃逸路径。每次工具调用就是一个有界动作加一个被捕获的结果。
八个工具
| 工具 | 行为 |
|---|---|
ssh_list_servers | 列出本机命名的服务器,不泄露凭据或密钥路径 |
ssh_connect | 从清单或 ~/.ssh/config(含 ProxyJump)开会话,存入一个 id |
ssh_disconnect | 关闭已存会话 |
ssh_exec | 在已存会话上跑命令,返回 stdout、stderr、退出码 |
ssh_quick_exec | 连接、跑一条命令、断开(无状态) |
ssh_list_dir | 通过 SFTP 列远程目录 |
ssh_upload | 通过 SFTP 上传本地文件 |
ssh_download | 通过 SFTP 下载远程文件 |
刻意不含:PTY/交互式 shell、sudo/su、端口转发、批量/并行执行。命令执行按设计就是非交互的。
为什么是单二进制
没有包管理器运行时,没有 node_modules,没有 pip 依赖树。安装期没有可被供应链投毒的环节,只有一个可哈希、可 pin 的编译产物。CGO_ENABLED=0 静态构建,能跑在任何地方;一台机器即可交叉编译到 Linux、Windows、macOS。Go 实现按传输、鉴权、清单、文件传输拆成聚焦的小文件,便于审计。
配置与运行
连接先解析本地服务器清单,然后(启用了 ad-hoc 主机时)解析 ~/.ssh/config。清单条目支持 ssh-agent、私钥、密码认证;密码和加密私钥的口令只存在操作系统 keyring。agent 认证在 Unix 用 SSH_AUTH_SOCK、在 Windows 用 OpenSSH agent 管道,且永不回退到密码认证。
手动启动可执行文件。它把 MCP Streamable HTTP 端点开在 http://127.0.0.1:2223/mcp,内嵌 GUI 在 http://127.0.0.1:2224。两个地址固定;任一必需端口不可用则启动失败。MCP 端点要求来自 SSH_MCP_AUTH_TOKEN 的 Authorization: Bearer 。GUI 可用 SSH_MCP_GUI=0 关闭。
servers.json 与 settings.json 放在运行的可执行文件旁边。GUI 与 MCP 共享同一份存储,所以保存的服务器与工具设置立即生效。Claude Desktop 的 Tool permissions 是另一个独立权限层;本服务不实现 Ask/Allow。主机密钥用 ~/.ssh/known_hosts,accept-new 语义(未知主机记录;变更的密钥拒绝)。
每个清单服务器可选配一个 SOCKS5 主机与端口。SOCKS5 用户名存在 servers.json;其密码留在 OS keyring。一个服务器不能同时用 SOCKS5 与 ProxyJump,但跳板机可以用它自己的 SOCKS5 路由。
旧 Windows 数据目录是 %APPDATA%\mcp-ssh-go;若存在,把 servers.json 与 settings.json 拷到新可执行文件旁,旧目录保留。密码与密钥口令在 OS keyring,不在这两个文件里。
环境变量
SSH_MCP_AUTH_TOKEN:必需,MCP HTTP 请求的 bearer token;用高熵、无空白字符的值。SSH_MCP_ALLOWED_KEY_DIRS:冒号/逗号分隔的额外目录,除~/.ssh与/etc/ssh外,可从这里读私钥与 ssh_config(当$HOME是指向 NFS/AD 家目录的符号链接时有用)。SSH_MCP_ENABLED_TOOLS:逗号分隔的工具列表,非空时覆盖settings.json(默认八个全开)。SSH_MCP_GUI:设为0关闭本地管理 GUI。SSH_MCP_MAX_OUTPUT_BYTES:exec 输出每路(stdout、stderr)返回给客户端的默认上限(默认131072,钳制在[1024, 524288])。
输出上限:按字节截,保护的是 token
ssh_exec / ssh_quick_exec 结果按路封顶(默认 128 KB,单次调用可用 max_output_bytes 覆盖,最高 512 KB)。超限输出返回上限的前约 75% + 后约 25%,并内嵌一个标记说明丢了多少、如何收窄命令(head/tail/grep)。这样 agent cat 一个几 MB 日志时,拿到的是可用、能自我纠正的结果,而不是比自己上下文窗口还大的工具结果。ssh_list_dir 同样最多返回 2000 条(外加真实总数)。
上限以字节计,但保护的是 token 数,比值取决于命令打印什么。散文/源码/普通日志约 4 字节/token,128 KB ≈ 32k token;密集数字/表格输出(如 2.651954E+02 列)约 1.7 字节/token,128 KB ≈ 78k token。所以对日志舒服的 128 KB 默认值,对 128k 模型可能是一半上下文窗口,对 32k 或 64k 模型则超过整个窗口 —— 而这类部署最可能把服务器指向求解器输出、数据转储或宽 CSV。一个会话里两次这样的结果自身就超过 128k 窗口。
按模型上下文窗口(而非字节数)来定 SSH_MCP_MAX_OUTPUT_BYTES。数字密集负载的粗略规则:上限约为 context_tokens × 1.7 ÷ 4 字节,这样一个被封顶的结果最多占窗口四分之一,一个会话能与对话并排放下几个。示例:128k token 模型读数字输出得约 54 KB;向下取整到 48 KB(SSH_MCP_MAX_OUTPUT_BYTES=49152),留出余量,头 36 KB + 尾 12 KB,一个会话约四次封顶读取。头+尾切分同理:批处理和求解器输出往往顶部是 banner 与配置、底部是结果与汇总统计,保留两端通常能保住问题真正关心的部分;只截头会返回配置却丢掉所有结果。单次调用提高上限(max_output_bytes,最高 512 KB),而不是给每次调用都提高默认值。
构建
go build -o mcp-ssh-go .
交叉编译:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o mcp-ssh-go .
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o mcp-ssh-go.exe .
Docker(Linux/amd64)
在 Linux 或用 Docker Buildx 构建:
docker buildx build --platform linux/amd64 --load -t mcp-ssh-go:local .
在 Linux 主机上用 host 网络运行,使固定的 loopback 监听在主机可用。命名卷挂到 /app 把 JSON 数据放在二进制旁;entrypoint 每次启动从镜像刷新二进制。
docker volume create mcp-ssh-go-data
docker run --rm --network host \
--env-file "$HOME/.config/mcp-ssh-go.env" \
-v mcp-ssh-go-data:/app \
-v "$SSH_AUTH_SOCK:/ssh-agent" \
-e SSH_AUTH_SOCK=/ssh-agent \
mcp-ssh-go:local
env 文件必须含 SSH_MCP_AUTH_TOKEN=,且应仅属主可读。用密钥文件认证时省略 agent socket 挂载;把密钥目录只读挂载并设 SSH_MCP_ALLOWED_KEY_DIRS 为容器内路径。不要把密钥或 token 打进镜像。容器不含、也不会自动共享宿主的 Secret Service;host 网络不暴露会话 D-Bus。SSH 密码认证、加密密钥口令、SOCKS5 密码需要可达的 Linux Secret Service 与 D-Bus 会话,显式挂载/配置允许的容器 UID;否则改用挂载的 SSH agent 或密钥文件。
接 MCP 客户端
让客户端连到已在运行的 HTTP 端点。提供同一 bearer token,但别把它提交到共享配置文件:
{
"mcpServers": {
"ssh": {
"type": "http",
"url": "http://127.0.0.1:2223/mcp",
"headers": { "Authorization": "Bearer " }
}
}
}
要暴露到 localhost 之外时,保持进程绑定 loopback,用反向代理终止 TLS 并转发 Authorization 头。上述容器示例针对 Linux,用 host 网络使同样的 loopback 端点可用。