ssh-mcp
ssh-mcp
一个集中式 MCP 网关,通过 Streamable HTTP 让 AI 代理对 SSH 基础设施拥有受控访问权限。
ssh-mcp 以单个 HTTP 服务运行。多个 AI 客户端——代理、CI 流水线、仪表盘——连接到一个网关。SSH 凭证保留在网关上。授权策略、审计日志和速率限制在任何 SSH 命令执行之前集中生效。
目录
Related MCP server: MCP SSH Orchestrator
架构
本地 stdio MCP(常见模式)
AI client
│
▼
local MCP process ──► SSH target每个代理运行自己的进程。SSH 凭证存在于每台机器上。没有集中控制。
ssh-mcp(集中式 HTTP 网关)
AI clients ───────┐
CI agents ────────┼──► ssh-mcp ──► SSH targets
Dashboards ───────┘ │
├─ API-key authentication
├─ per-client authorization
├─ rate limiting
├─ audit logging
└─ connection pooling单个部署即可服务所有客户端。凭证、策略和日志集中存放在一处。
为什么选择 ssh-mcp?
集中式 HTTP 网关——一个部署通过 Streamable HTTP 服务所有 AI 代理、CI 流水线和仪表盘
按客户端授权——不同 API 密钥在不同服务器上授予不同的命令集
分层命令策略——阻止模式、危险 shell 检测和按目标白名单协同工作
集中式 SSH 访问——SSH 凭证存放在网关上,而不是每个代理的机器上
审计跟踪——每条命令、每个客户端、每个结果——带请求追踪的结构化 JSONL 日志
运维韧性——连接池、熔断器以及带指数退避的重试
可观测性——用于监控的 Prometheus 指标和健康检查端点
多代理访问控制
不同的代理需要不同的权限。ssh-mcp 在网关上强制执行这一点:
monitoring agent → API key A → read-only commands → all servers
deployment agent → API key B → deploy commands → web servers only
database agent → API key C → db commands → database server only ┌─ monitoring agent (read-only, all servers)
├─ deployment agent (deploy commands, web only)
MCP clients ──────┼─ database agent (db commands, db server only)
└─ ...
│
▼
ssh-mcp
│
centralized policies
│
┌──────────┼──────────┐
▼ ▼ ▼
web db monitoring
servers servers servers演示此设置的最小配置:
{
"version": 1,
"ssh_targets": {
"web-1": { "host": "10.0.1.10", "username": "deploy" },
"db-1": { "host": "10.0.1.20", "username": "dbadmin" }
},
"allowed_commands": {
"default": {
"web-1": { "allow": ["uptime", "df -h", "free -m"] }
},
"api_keys": {
"deploy-key": {
"web-1": { "allow": ["systemctl restart app", "deploy *"] }
},
"db-key": {
"db-1": { "allow": ["systemctl restart postgres", "pg_dump *"] }
}
}
}
}问题
大多数 MCP SSH 服务器以本地 stdio 进程运行——每个客户端一个,没有共享状态、没有集中式授权、也没有审计跟踪。当多个 AI 代理、CI 流水线或仪表盘需要 SSH 访问时,每个都会独立管理自己的 SSH 密钥并运行自己的 MCP 进程。这会导致:
没有集中式访问控制——每个客户端自行决定可以运行什么
没有审计跟踪——运维团队看不到命令
SSH 密钥泛滥——密钥散落在每台运行代理的机器上
没有速率限制——失控的代理可能压垮目标
没有连接池——每个客户端独立打开和关闭 SSH 会话
ssh-mcp 通过将单个 MCP 服务器部署为 HTTP 网关来解决这个问题。所有客户端连接到它;它连接到你的 SSH 目标。授权、身份验证、速率限制、连接池和审计日志都在一处完成。
使用场景
多代理服务器管理
运行一组具有不同访问级别的 AI 代理。部署代理可以在 Web 服务器上执行 systemctl restart nginx;监控代理可以在任意位置执行 journalctl;数据库代理只能在数据库服务器上运行 psql。每个代理使用自己的 API 密钥进行身份验证;每个密钥都有各自的权限集。
CI/CD 流水线集成
将你的 CI 流水线指向 ssh-mcp,而无需在每个 runner 上管理 SSH 密钥。每条流水线一个 API 密钥、针对 CI 子网的基于网络的规则以及命令白名单,确保你的部署脚本只运行应运行的内容——不多不少。
集中式日志与配置检索
使用 ssh_download_file 从远程服务器拉取日志、配置文件或数据库转储,而无需离开你的 MCP 客户端。8 层路径验证和沙箱根目录设置确保文件传输保持在安全边界之内。
服务器健康仪表盘
构建一个由 MCP 驱动的仪表盘,在整支服务器集群中查询 uptime、free、df 和 ps。连接池复用 SSH 会话,熔断器隔离故障目标,/metrics 处的 Prometheus 指标接入你现有的监控体系。
合规与审计
每条命令都会以结构化 JSONL 记录:谁在哪个服务器上从哪个 IP 运行了什么命令、是否被允许、耗时多久。matched_via 字段精确追踪是哪个授权层做出的决定。配置变更会单独记录变更前后的状态。
安全模型
ssh-mcp 在每一层都应用纵深防御。完整的安全模型记录在 docs/SECURITY.md 中。
安全边界: ssh-mcp 在 SSH 之前增加了一层授权、身份验证和审计。它不会替代底层 SSH 账户的权限。如果某条命令被允许,SSH 用户将以该账户拥有的任何权限来执行它。网关本身应通过 TLS 和网络访问控制加以保护。日志可能包含命令输出,应相应地加以处理。
命令授权链
命令通过有序的分层链进行评估。如果任何一层拒绝,请求就会在该处停止:
层 | 检查内容 | |||
1. 目标验证 | 服务器名称是否已知? | |||
2. | 命令是否匹配被阻止的正则表达式? | |||
3. 危险模式 | 是否包含 | |||
4. 重定向防护 | shell 重定向是否指向 | |||
5. 分段 | 去除重定向并按 |
| ||
6. | 所有客户端的允许/拒绝规则 | |||
7. | 按密钥的允许/拒绝规则 | |||
8. | 按 CIDR 的允许/拒绝规则 | |||
9. 拒绝 | 隐式回退 |
身份验证
API 密钥通过 X-API-Key 或 Authorization: Bearer 请求头发送。密钥使用 PBKDF2-HMAC-SHA256 哈希(100,000 次迭代、随机 16 字节盐),并通过常数时间比较进行验证。原始密钥永远不会存储。
输入清理
命令、目标名称和日志字符串在处理前会进行清理:去除空字节、移除控制字符、NFKC 规范化,并对 block_patterns 执行 ReDoS 防护。
路径遍历防护
SFTP 传输经过 8 层路径验证,包括空字节检查、控制字符去除、点段规范化、符号链接解析和沙箱根目录强制执行。
速率限制
按客户端 IP 的滑动窗口速率限制器(60 次请求 / 60 秒,/health 除外)。违反时返回 HTTP 429 并附带 Retry-After。
速率限制可在 settings.rate_limit 下配置:
"settings": {
"rate_limit": {
"enabled": true, // set false to disable entirely
"max_requests_per_minute": 60, // max requests per client IP in the window
"window_seconds": 60.0, // sliding-window duration
"cleanup_interval_seconds": 300.0 // expired-entry GC interval
}
}注意: 速率限制器在容器启动时根据初始配置构建一次,并且不会在配置热重载时重建。要禁用速率限制,必须在启动时存在的配置(例如挂载卷中的
config/ssh-mcp-config.json)中将settings.rate_limit.enabled设为false。这对于高流量客户端或从单个 IP 发出大量请求的测试套件很有用。
快速开始
先决条件
安装了 Docker Compose 的 Docker
用于你要访问的服务器的 SSH 密钥对(或按目标的密码)
1. 创建目录
mkdir -p config logs
ssh-keygen -t ed25519 -f ssh_key -N ""
cp default-config.json config/ssh-mcp-config.json2. 添加 SSH 目标
打开 config/ssh-mcp-config.json 并添加一个目标:
{
"version": 1,
"ssh_targets": {
"web-server": {
"host": "192.168.1.10",
"port": 22,
"username": "deploy",
"private_key": "/app/ssh_key"
}
},
"block_patterns": [ "\\brm\\s+-rf\\b", "\\bdd\\s+if=" ],
"allowed_commands": {
"default": [
{ "targets": ["*"], "commands": ["hostname", "uptime", "free", "df", "ps", "ls", "cat"] }
]
},
"settings": {}
}3. 启动服务器
docker compose up -d --build4. 验证运行状态
curl http://localhost:9080/health
# {"status": "ok", "connection_pool": {...}}5. 连接 MCP 客户端
任何支持 Streamable HTTP 的 MCP 客户端都可以连接。将其指向 http://localhost:9080/mcp 并附带 API 密钥请求头。详情请参阅 MCP 客户端配置。
6. 列出服务器并运行命令
curl -X POST http://localhost:9080/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "ssh_list_servers",
"arguments": {}
}
}'
curl -X POST http://localhost:9080/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "ssh_execute_command",
"arguments": {"server_name": "web-server", "command": "uptime"}
}
}'MCP 客户端配置
任何支持 Streamable HTTP 传输的 MCP 客户端都可以连接。配置格式因客户端而异——请使用下面的 URL 和请求头。
设置 | 值 |
传输 | Streamable HTTP |
URL |
|
身份验证 |
|
通用 Streamable HTTP 配置
{
"mcpServers": {
"ssh": {
"url": "http://localhost:9080/mcp",
"headers": {
"Authorization": "Bearer <your-api-key>"
}
}
}
}Python 客户端
import requests
MCP_URL = "https://ssh-mcp.example.com/mcp"
API_KEY = "your-api-key"
def call_tool(name: str, arguments: dict) -> dict:
response = requests.post(
MCP_URL,
headers={
"Content-Type": "application/json",
"X-API-Key": API_KEY,
},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": name, "arguments": arguments},
},
)
response.raise_for_status()
return response.json()
print(call_tool("ssh_list_servers", {}))
print(call_tool("ssh_execute_command", {
"server_name": "web-server",
"command": "uptime",
}))原始 JSON-RPC
将工具调用作为 JSON-RPC tools/call 请求发送到 /mcp:
curl -X POST http://localhost:9080/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "ssh_execute_command",
"arguments": {"server_name": "web-server", "command": "uptime"}
}
}'工具
所有工具调用都是发送到 /mcp 的 JSON-RPC tools/call 请求。所有工具都返回字符串(JSON 或纯文本)。
工具 | 参数 | 描述 |
| (无) | 列出已配置的 SSH 目标(host、port、username——不含机密信息) |
|
| 列出当前客户端可以在目标上运行的命令(default + api_key + network 规则的并集) |
|
| 通过 SSH 执行命令;返回 stdout(stderr 以 |
|
| 通过 SFTP 下载文件;授权等同于 |
|
| 通过 SFTP 上传文件;授权等同于 |
|
| 通过运行目标的 |
示例
# List available servers
call_tool("ssh_list_servers", {})
# {"web-server": {"host": "192.168.1.10", "port": 22, "username": "deploy"}}
# List what this client can run on web-server
call_tool("ssh_list_allowed_commands", {"server_name": "web-server"})
# ["cat", "df", "du", "free", "grep", "head", "hostname", ...]
# Execute a command
call_tool("ssh_execute_command", {
"server_name": "web-server",
"command": "uptime",
})
# " 07:12:33 up 10 days, 2:15, 1 user, load average: 0.08, 0.03, 0.01"
# Download a file
call_tool("ssh_download_file", {
"server_name": "web-server",
"remote_path": "/etc/hostname",
})
# "web-server\n"
# Upload a file
call_tool("ssh_upload_file", {
"server_name": "web-server",
"remote_path": "/tmp/backup.sql",
"content": "CREATE TABLE ...;\n",
"permissions": "0640",
})
# "OK: Uploaded 19 bytes to /tmp/backup.sql"
# Check SSH connectivity
call_tool("ssh_check_connection", {"server_name": "web-server"})
# {"success": true, "output": "ping", "error": null, "exit_code": 0, "checkcommand": "echo ping"}
# Check with custom timeout
call_tool("ssh_check_connection", {"server_name": "web-server", "timeout": 5})关于 sudo 的说明: 没有
sudo_password参数。如果 sudo 需要密码,则密码来自配置中目标的password字段。sudo标志会使用sudo -S -p ''(密码来自配置)或sudo -n(免密码)进行包装。
错误响应
失败时,工具返回:
{
"error": true,
"error_type": "AuthorizationError",
"message": "Command rejected: target 'foo' not found",
"retryable": false,
"request_id": "abc-123"
}常见的 error_type 值:AuthorizationError、PathValidationError、FileTransferError、SSHAuthenticationError、SSHTimeoutError、MCPSSHError。对于 SSHTimeoutError,retryable 标志为 true。违反速率限制则改为返回 HTTP 429。
配置
配置文件位置
服务器读取 <config_dir>/ssh-mcp-config.json。通过 --config CLI 标志或 MCP_SSH_CONFIG_PATH 环境变量设置 config_dir(默认值:/config)。如果文件不存在,服务器会写入随附的 default-config.json。
顶层结构
{
"version": 1,
"ssh_targets": { ... },
"block_patterns": [ ... ],
"allowed_commands": {
"default": [ ... ],
"api_keys": [ ... ],
"networks": [ ... ]
},
"settings": { ... }
}配置在加载时会根据 config.schema.json(JSON Schema Draft 2020-12)进行校验。未知键会导致硬错误。
ssh_targets
一个以服务器标识符为键的对象。每个目标都需要 host、port、username,以及 private_key 或 password 中的至少一个。
"ssh_targets": {
"web-server": {
"host": "192.168.1.10",
"port": 22,
"username": "deploy",
"private_key": "/app/ssh_key",
"checkcommand": "echo ping"
}
}字段 | 必填 | 默认值 | 描述 |
| 是 | — | 主机名或 IP 地址 |
| 否 |
| SSH 端口 |
| 是 | — | SSH 用户名 |
| * | — | 服务器文件系统上 SSH 私钥文件的路径 |
| * | — | SSH 密码(也可以通过 |
| 否 |
| 由 |
* private_key 或 password 中至少需要提供其一。
private_key是服务器文件系统上的路径(在 Docker 中,会挂载到容器内),而不是内联密钥。
block_patterns
一个正则表达式模式列表。任何匹配模式的命令都会被拒绝,无论其他允许列表层如何。模式在加载时会针对灾难性回溯结构进行筛查(ReDoS 防护),并在运行时以超时保护进行编译。
allowed_commands
三个子对象控制每个客户端可以运行哪些命令:
default— 适用于所有客户端的规则(除非更具体的层先作出决定)api_keys— 按密钥的规则,通过key_hash匹配networks— 按 CIDR 的规则,通过客户端源 IP 匹配
每个规则都有一个 targets 列表(服务器 ID,或 "*" 表示全部)和一个 commands 列表(基础命令名,或 "*" 表示任意命令)。
"allowed_commands": {
"default": [
{ "targets": ["*"], "commands": ["hostname", "uptime", "free", "df", "ps"] }
],
"api_keys": [
{
"name": "ci-bot",
"key_hash": "pbkdf2:sha256:100000$<salt>$<hash>",
"rules": [
{ "targets": ["web-server"], "commands": ["systemctl", "journalctl"] }
]
}
],
"networks": [
{
"name": "home-lan",
"range": "192.168.1.0/24",
"rules": [
{ "targets": ["*"], "commands": ["*"] }
]
}
]
}settings
设置 | 默认值 | 描述 |
|
| 返回给客户端的命令输出的最大字节数(整数或大小字符串) |
|
| 命令超时的硬上限(秒) |
|
| 瞬时 SSH 失败的重试次数 |
|
| 指数退避基数(秒) |
|
| 每个目标在熔断器打开前的失败次数 |
|
| 熔断器打开后的恢复超时(秒) |
|
| 日志级别:DEBUG、INFO、WARNING、ERROR |
|
| 日志条目中存储的输出最大字符数 |
|
| 对轮转的日志文件进行 Gzip 压缩 |
|
| 每个目标的最大连接池 SSH 连接数 |
|
| 空闲连接超时(秒) |
|
| 连接池清理间隔(秒) |
|
| 所有目标的全局上限;超出时返回 HTTP 503 |
|
| 配置重新加载之间的最小间隔; |
|
| 可信反向代理 IP(IPv4/IPv6) |
SFTP 设置(settings.sftp)
设置 | 默认值 | 描述 |
|
| SFTP 路径校验的根目录 |
|
| 允许的 SFTP 路径最大长度(字节); |
密钥
SSH 目标密码和 API 密钥哈希可以与主配置分离,放入 <config_dir>/secrets.json 或 MCP_SSH_SECRET_* 环境变量中。优先级:
environment variables > secrets.json > ssh-mcp-config.json密钥来源 | 作用 |
| 按目标的 |
| 覆盖 |
| 覆盖 |
<TARGET_ID> 和 <KEY_NAME> 会转换为大写,- 变为 _。API 密钥值必须是哈希字符串,而不是原始密钥。
环境变量与 CLI 标志
环境变量 | CLI 标志 | 默认值 | 旧版回退 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| — |
| — |
| — |
| — | (启用 API 时必填) | — |
— |
|
| — |
— |
| — | — |
CLI 标志优先于环境变量。任何 settings 键都可以在运行时通过 MCP_SSH_SETTING_<KEY> 覆盖(转换为大写,- 变为 _)。
热重载
服务器会轮询配置文件以检测更改(间隔 15 秒,去抖 2 秒)。检测到更改后,它会重新加载、校验,并以原子方式替换为新配置。配置更改回调(授权规则重建、连接池刷新)会在替换成功后运行。在可用时使用基于 watchdog 的文件监控。
可观测性
健康检查
GET /health 返回 {"status": "ok"} 以及连接池统计信息。容器的 HEALTHCHECK 使用此端点。
Prometheus 指标
GET /metrics 在专用注册表上暴露指标,所有指标均以 mcpssh_ 为前缀:
指标 | 类型 | 标签 |
| Counter |
|
| Counter |
|
| Histogram |
|
| Counter |
|
| Histogram |
|
| Gauge |
|
| Gauge |
|
| Counter |
|
结构化日志
mcp-ssh 服务器支持通过配置文件中的 settings.logging.log_targets 配置可插拔的日志目标。每个目标都是一个独立的驱动程序,接收所有日志条目。
默认行为
默认情况下,日志条目以人类可读的文本格式写入 stdout。这适用于容器日志由运行时捕获的 Docker 环境。
日志目标类型
目标 | 配置值 | 格式 | 描述 |
标准输出 |
| Text | 写入标准输出。默认目标。 |
JSON 文件 |
| JSONL | 将JSON对象逐行写入文件。 |
文本文件 |
| Text | 将人类可读的文本写入文件。 |
配置
{
"settings": {
"log_level": "INFO",
"logging": {
"log_targets": [
{ "target": "stdout" },
{ "target": "jsonfile", "filepath": "logs/ssh-mcp.log" }
],
"max_log_output": 4096,
"compress_rotated": true
}
}
}日志级别
配置文件: 设置
settings.log_level来控制默认级别。环境变量: 设置
MCP_SSH_LOG_LEVEL来覆盖配置文件中的默认值(例如MCP_SSH_LOG_LEVEL=DEBUG)。每个目标: 每个日志目标可以有自己的
log_level来覆盖默认值。
旧版配置
如果 settings.logging 不存在,服务器将回退到日志目录(默认为 /logs)中的单个 JSONL 文件目标。这保持与现有配置的向后兼容性。
文本格式
标准输出和文本文件目标使用以下格式:
2025-01-15 10:30:00 INFO ssh_execute_command: Command executed on server1JSON 格式
JSON 文件目标每行写入一个 JSON 对象:
{"timestamp": "2025-01-15T10:30:00+00:00", "event": "ssh_execute_command", "level": "INFO", "message": "Command executed on server1", "request_id": "abc-123", "log_level": "INFO", "log_format_version": 1}文件轮转
基于文件的目标超过 max_file_size_mb(默认:10 MiB)时会进行轮转,保留 backup_count 个备份(默认:5)。当 compress_rotated 为 true 时,轮转文件会以 gzip 压缩。
配置变更事件
事件 | 含义 |
| 启动时加载初始配置 |
| 从磁盘重新读取配置(带有 |
| 已应用模式迁移( |
| 已复制捆绑的默认配置 |
| 回退到内存中的默认值 |
| 配置更改回调引发异常 |
配置 API 与 Web 仪表盘
统一容器包含可选的配置 API 与 Web 仪表盘——这是针对您的 SSH 策略、目标、命令规则和备份的完整管理平面。无需编辑配置文件。此功能默认禁用。
功能一览
Web 仪表盘 — 一个响应式单页应用,包含 5 个页面:SSH 目标、阻止模式、命令规则、设置和备份。使用您的 API 令牌登录,即可在浏览器中管理一切。
REST API — 对每个配置部分提供完整的增删改查,外加配置验证、API 密钥哈希、备份管理和内联 SSH 连通性测试。
API 密钥哈希工具 — 将明文 API 密钥哈希为可直接用于配置的 PBKDF2 字符串。不再需要猜测哈希格式。
备份与恢复 — 每次写入时自动备份配置;可从仪表盘或 API 列出、恢复或删除备份。
原子化、线程安全的写入 — 所有配置写入都会经过验证、通过线程锁串行化,并原子化地写入磁盘。
Swagger UI 与 ReDoc — 自动生成的交互式 API 文档,位于
/api/docs和/api/redoc。
启用配置 API
在您的 compose.yaml 或 .env 文件中设置以下环境变量:
变量 | 默认值 | 描述 |
|
| 设置为 |
| (启用时必填) | 用于对 API 请求进行身份验证的 Bearer 令牌 |
services:
mcp-ssh:
environment:
CONFIG_API_ENABLED: "true"
CONFIG_API_TOKEN: "your-secret-token-here"API 端点
所有端点都挂载在 /api 上,与 MCP 服务器共用同一个 Starlette ASGI 应用。
健康检查与工具
方法 | 路径 | 描述 |
|
| 配置 API 的健康检查(无需身份验证) |
|
| 将明文 API 密钥哈希为 PBKDF2-HMAC-SHA256 字符串 |
|
| 返回配置 JSON Schema(无需身份验证) |
|
| 验证配置字典而不写入磁盘 |
配置
方法 | 路径 | 描述 |
|
| 获取完整配置(对机密信息进行脱敏) |
|
| 替换完整配置 |
|
| 获取单个配置部分( |
|
| 替换单个配置部分 |
SSH 目标
方法 | 路径 | 描述 |
|
| 获取特定的 SSH 目标(机密信息已剥离) |
|
| 创建或替换 SSH 目标 |
|
| 删除 SSH 目标 |
|
| 通过目标的 |
命令规则
方法 | 路径 | 描述 |
|
| 列出允许的命令规则(通过 |
|
| 替换允许的命令规则(通过 |
阻止模式
方法 | 路径 | 描述 |
|
| 列出阻止模式(通过 |
|
| 替换所有阻止模式 |
|
| 追加一个阻止模式 |
|
| 按索引替换单个阻止模式 |
|
| 按索引移除单个阻止模式 |
备份
方法 | 路径 | 描述 |
|
| 列出配置备份(最新的在前) |
|
| 从备份恢复配置 |
|
| 删除备份文件 |
身份验证
所有 API 请求(/api/health 和 /api/config/schema 除外)都要求在 Authorization 头中携带 Bearer 令牌:
curl -H "Authorization: Bearer your-secret-token-here" http://localhost:9080/api/configWeb 仪表盘
启用后,可在 http://localhost:9080/ui/ 访问响应式单页应用——这是一个使用 Tailwind CSS 构建的完整管理界面。无需页面刷新,每次操作都有 toast 通知,并通过模态对话框进行编辑。
页面 | 功能 |
SSH 目标 | 查看、添加、编辑、删除目标;通过 |
阻止模式 | 添加、编辑(按索引)、删除单个模式;查看完整模式列表 |
命令规则 | 编辑默认、API 密钥和网络规则;包含目标和命令列表的完整规则编辑器 |
设置 | 编辑所有服务器设置:SFTP 沙箱、速率限制、日志记录、连接池、断路器等等 |
备份 | 列出、恢复和删除配置备份;每个备份都有时间戳和大小 |
其他功能:
基于令牌的登录,支持会话管理(存储在
sessionStorage中)配置验证 — 更改在写入之前会经过验证
API 密钥哈希 — 可直接在仪表盘中哈希明文密钥
响应式设计 — 适用于桌面端和移动端
Toast 通知 — 每次操作都有成功/错误反馈
Swagger / ReDoc
交互式 API 文档由 FastAPI 自动生成:
Swagger UI:
http://localhost:9080/api/docsReDoc:
http://localhost:9080/api/redoc
部署
Docker Compose
compose.yaml 定义了一个 mcp-ssh 服务,该服务同时承载 MCP 服务器以及(可选的)配置 API 与 Web 仪表盘。配置 API 通过 CONFIG_API_ENABLED 环境变量启用(默认:false)。
mcp-ssh — MCP SSH 网关 + 配置 API
主机路径 | 容器路径 | 模式 |
|
| rw |
|
| rw |
|
| ro |
|
| ro |
暴露在主机端口 9080 上(映射到容器端口 8080)。运行镜像为 python:3.13-alpine,并带有哈希固定的摘要。进程由非 root 用户 mcpssh 运行。在构建时于 sbom 阶段生成 CycloneDX SBOM。
配置 API 与 Web 仪表盘(可选)
通过在你的 .env 文件或环境中设置 CONFIG_API_ENABLED=true 来启用配置 API:
# Generate an auth token
openssl rand -hex 32CONFIG_API_ENABLED=true
CONFIG_API_TOKEN=<your-token>启用后,配置 API 将挂载在 /api 上,与 MCP 网关共用同一个 HTTP 服务器。它提供:
REST API,位于
http://localhost:9080/api/...— 对 SSH 目标、阻止模式、命令规则、备份和设置提供完整的增删改查Web 仪表盘(GUI),位于
http://localhost:9080/ui/— 用于可视化策略管理的单页应用(SSH 目标、阻止模式、命令规则、设置、备份)API 文档,位于
http://localhost:9080/api/docs(Swagger UI)和http://localhost:9080/api/redoc(ReDoc)
Makefile
命令 | 描述 |
| 构建 Docker 镜像( |
|
|
|
|
| 运行单元测试 |
| 运行 config-api 单元测试 |
| 构建测试镜像,运行集成测试 |
| 清理测试产物和容器 |
从 GHCR 拉取
Docker 镜像会自动构建并发布到 GitHub Container Registry:
docker pull ghcr.io/gelse/ssh-mcp:latest局限性与威胁模型
ssh-mcp 不是什么
不是 shell。 你无法获得交互式终端会话。所有执行都是一次性的命令调用。
不是文件管理器。 SFTP 仅限于单文件上传/下载,并带有路径验证和沙箱强制。不支持目录列表,不支持递归操作。
不是网络防火墙。 限流按 IP 进行,采用固定的默认值。它防护的是失控的客户端,而非坚决的攻击者。
威胁模型
威胁 | 缓解措施 |
通过命令链进行的命令注入( | 命令分段 — 每个分段都会运行完整的授权链 |
对敏感路径的 shell 重定向( | 重定向目标守卫会拒绝重定向到 |
SFTP 中的路径遍历 | 8 层路径验证:空字节检查、控制字符剥离、点段归一化、符号链接解析、沙箱根目录强制 |
通过 | 加载时静态筛查 + 运行时超时守卫 |
API 密钥暴力破解 | PBKDF2-HMAC-SHA256 配合恒定时间验证;按 IP 限流 |
日志注入 | 所有用户可控字段在记录日志前进行换行符清理 |
配置中的机密信息 |
|
不在范围之内
TLS 终止(由你的反向代理处理)
API 密钥之外的用户认证(无 OAuth,应用层无 mTLS)
SSH 会话多路复用(无 tmux/screen 透传)
审计日志防篡改(日志是本地文件;请使用你自己的日志传输来保证不可变性)
开发
项目结构
server.py— FastMCP 应用工厂 + CLI 入口点lib/— 30 个单一职责模块(认证、配置、SSH 客户端、文件传输、日志等)config-api/— 配置 API + Web 仪表盘(FastAPI,当CONFIG_API_ENABLED=true时挂载于/api)tests/— 36 个单元测试文件 + 使用真实 Docker 容器的集成测试
技术栈
Python 3.13, FastMCP 3.4.x, paramiko 5.0, Starlette 1.4, FastAPI 0.115+, Pydantic 2.10+, httpx 0.28+, uvicorn 0.34+
运行测试
# Unit tests (fast inner loop)
source .venv/bin/activate
python -m pytest tests/test_<module>.py -x
# Full unit test suite
make test
# Integration tests (requires Docker)
make integrationtest添加新工具
AGENTS.md 中的完整示例 会端到端地演示如何添加一个新的 @mcp.tool() 处理器:常量、类型、重新导出、处理器、测试、提交。
无 Lint/类型检查工具
项目没有 ruff、mypy、pyright 或 flake8 配置。格式化遵循 .editorconfig 默认值(Python 使用 4 个空格,每行 88 个字符)。
路线图
用于可视化策略管理的配置 GUI
许可证
MIT 许可证 — 详情请参阅 LICENSE。
This server cannot be installed
Maintenance
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables secure remote access operations through SSH, SFTP, rsync, VPN, and tunneling with enterprise-grade policy enforcement and audit logging. Provides AI assistants with secure, policy-driven access to remote systems while maintaining comprehensive audit trails and zero-trust security.1Apache 2.0
- AlicenseBqualityAmaintenanceProvides policy-driven, auditable SSH access to server fleets for AI assistants with zero-trust security controls, command whitelisting, and comprehensive audit logging to safely manage infrastructure.1327Apache 2.0
- AlicenseAqualityCmaintenanceEnables 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.1022MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to securely execute SSH commands on remote servers with connection pooling, session isolation, and a web audit panel.3MIT
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.
Agent payments, API key vaulting, and governed mandates. Agents spend within user-defined limits.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gelse/ssh-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server