mcpField
O
opsgate

opsgate

本地服务
polymatx.dev云与基础设施12 周前更新

关于 opsgate

opsgate 是供 AI Agent 检查和管理基础设施的 MCP 服务,不向 Agent 提供通用 Shell。它通过类型化操作、策略模式、变更审批和防篡改审计日志控制主机命令的执行。

主要功能

  1. 01不使用 Shell 字符串的类型化运维工具
  2. 02observe、operate 和 full 策略模式
  3. 03变更操作需审批
  4. 04按主机和工具限制目标
  5. 05哈希链审计日志
  6. 06本地 stdio 与认证 HTTP 传输

适用场景

  1. 01检查服务、端口、进程和磁盘用量
  2. 02查看 Docker 容器与系统日志
  3. 03审批并审计服务或容器变更

MCP 相关

通过 stdio 或 HTTP 向 MCP 客户端提供类型化基础设施工具。变更操作可通过 MCP elicitation 请求人工审批;observe 模式为只读。

配置

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

mcp.json · 通用配置
{
"mcpServers": {
"polymatx-opsgate": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/polymatx/opsgate:0.1.3"
]
}
}
}
启动命令docker run -i --rm ghcr.io/polymatx/opsgate:0.1.3

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

安装包
ocighcr.io/polymatx/opsgate:0.1.3

工具

还没有获取到工具列表

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

详细介绍

opsgate

AI Agent 与服务器之间的安全关卡。

一个 MCP 服务,让 Claude、Cursor 或任何 AI Agent 操作真实基础设施, 而无需给它 root shell。

CI Go Report Card Go Reference License: MIT Go 1.26+ MCP

看看它的样子 · 30 秒试用 · 亲自试着攻破它 · 工具 · 安全模型 · 参与贡献

Agent 调用 opsgate 的过程:shell 注入、路径遍历和观察模式下的重启操作都被拒绝,没有任何操作执行,每次尝试都记录在哈希链审计日志中

看看它的样子

你提出问题。Agent 使用只读工具调查并给出答案。

你: 我的服务器上有东西在监听 8000 端口,但我不记得是什么。帮我查查。

▸ listening_ports(host: "hetzner")

  Netid State  Recv-Q Send-Q  Local Address:Port  Process
  tcp   LISTEN 0      511         0.0.0.0:443     users:(("nginx",pid=249619),…)
  tcp   LISTEN 0      511         0.0.0.0:80      users:(("nginx",pid=249619),…)
  tcp   LISTEN 0      4096        0.0.0.0:22      users:(("sshd",pid=237597),…)
  tcp   LISTEN 0      151       127.0.0.1:3306    users:(("mysqld",pid=237695),…)
  tcp   LISTEN 0      5         127.0.0.1:8000    users:(("python3",pid=237608),…)

▸ service_list(host: "hetzner", state: "running")

  docker.service       loaded active running  Docker Application Container Engine
  mysql.service        loaded active running  MySQL Community Server
  nginx.service        loaded active running  A high performance web server…
  php8.3-fpm.service   loaded active running  The PHP 8.3 FastCGI Process Manager
  pm2-root.service     loaded active running  PM2 process manager

Claude: 8000 端口上运行着一个普通的 python3 进程(pid 237608),仅绑定到 localhost, 因此外部无法访问。它不受 systemd 管理——没有对应的单元——这意味着重启后它不会继续运行。 最有可能是有人在 shell 中手动启动的。

现在试试会改变状态的操作。在 operate 模式下,Agent 不能直接执行:

你: 好,nginx 配置改好了。重新加载它。

▸ nginx_test(host: "hetzner")
  nginx: configuration file /etc/nginx/nginx.conf test is successful

▸ nginx_reload(host: "hetzner")

  ┌─ opsgate: approval required ─────────────────────────────────┐
  │ allow nginx_reload on host "hetzner"?                        │
  │                                                             │
  │ The nginx configuration passed validation.                  │
  │ This will reload nginx.                                     │
  │                                                             │
  │ Command: 'nginx' '-s' 'reload'                               │
  │                                        [ Approve ]  [ Deny ] │
  └─────────────────────────────────────────────────────────────┘

你点击 Deny,什么也不会发生——Agent 会收到操作员拒绝了请求的通知。点击 Approve,操作就会执行, 并记录到审计日志中。无论哪种情况,你都能先看到确切的命令。

(上方的工具输出来自真实服务器;提示词由你的 MCP 客户端呈现,因此显示方式取决于该客户端。)

30 秒试用(无需服务器)

opsgate 无需任何配置文件即可运行——在本机以只读模式运行:

go install github.com/polymatx/opsgate/cmd/opsgate@latest
opsgate serve
opsgate v0.1.3 | mode=observe | default_host=local | audit=~/.opsgate/audit.jsonl

就这么简单。无需配置,无需 SSH,且默认处于 observe 模式——因此它可以读取你的磁盘使用情况和日志, 但会拒绝执行任何更改:

▸ disk_usage()          → works
▸ service_restart(...)  → Refused by opsgate policy: host is in observe mode

将客户端连接到它,然后询问“这台机器上什么东西占用了磁盘空间?”准备好连接真实主机后,运行 opsgate init,即可将初始配置写入 ~/.opsgate/config.yaml。

哪些客户端可以访问它

opsgate 在你的机器上(或服务器本身)运行,并持有你的 SSH 密钥,因此需要使用哪种传输方式, 取决于客户端实际运行的位置:

客户端传输方式原因
Claude Code(终端)stdio在你的机器上原生运行并启动二进制文件
Claude Desktop、Cursor、VS Codestdio同上——本地进程
云端或沙盒环境(网页聊天、基于 VM 的 Agent)HTTP无法启动本地进程,甚至无法查看你的 SSH 密钥——只能访问 URL

如果客户端提示它无法访问你的服务器,那它几乎肯定属于沙盒环境。请改用 HTTP, 而非 stdio。

在公开这个端点之前,请先阅读该部分的警告:它会在你将其暴露到的网络上提供基础设施管理 API。

将其接入客户端
Claude Code

作为插件:包含 MCP 服务、一个 server-ops 技能,用于教会 Agent 先诊断再行动,并将拒绝视为最终结果;还包含一个只读的 /opsgate:health-check [host] 命令。

/plugin marketplace add polymatx/opsgate
/plugin install opsgate@opsgate

该插件会运行 opsgate serve,后者读取 ~/.opsgate/config.yaml(opsgate init 会写入一个初始配置文件)。它绝不会读取你当前打开的项目中的配置。 也可以只添加 MCP 服务:

claude mcp add opsgate -- opsgate serve --config ~/.opsgate/config.yaml
Claude Desktop / Cursor(claude_desktop_config.json 或 .cursor/mcp.json)
{
  "mcpServers": {
    "opsgate": {
      "command": "opsgate",
      "args": ["serve", "--config", "/absolute/path/to/opsgate.yaml"]
    }
  }
}
Docker(ghcr.io/polymatx/opsgate)

适用于通过 SSH 访问的主机。在容器内,local 指容器本身。

docker run -i --rm \
  -v ~/.opsgate:/root/.opsgate \
  -v ~/.ssh:/root/.ssh:ro \
  ghcr.io/polymatx/opsgate serve

该镜像也用于将 opsgate 列入 MCP Registry,名称为 io.github.polymatx/opsgate。

任意客户端,通过 HTTP 接入(云端/沙盒客户端需要此方式)
http:
  addr: "127.0.0.1:8080"
  auth_token: "a-long-random-token"   # sent as: Authorization: Bearer <token>
opsgate serve --http 127.0.0.1:8080

最简洁的部署方式是在 opsgate 管理的服务器上运行 opsgate——这样完全不需要 SSH 密钥(default_host: local)——并置于 TLS 终止反向代理之后:

client ──HTTPS──▶ nginx / Caddy ──▶ 127.0.0.1:8080  opsgate
                  + Bearer token

除非设置了 http.auth_token,否则 opsgate 拒绝在非回环地址上提供 HTTP 服务。

警告

暴露此端点意味着在该网络上提供基础设施管理 API,而 Bearer 令牌将成为唯一的保护措施。如果必须公开:请保持 mode: observe,为令牌生成至少 32 字节的随机值,要求使用 TLS,并在代理处添加 IP 允许列表。将其绑定到 VPN 接口(WireGuard、Tailscale),而不是公开互联网,要安全得多——只有你自己的设备能访问,其他人都不能。

另请注意,operate 模式需要有人类能够响应审批提示。无人值守的 HTTP 部署通常意味着使用 observe。

此方案解决的问题

如今,要让 AI Agent 运行你的服务器,就意味着通过 SSH 给它 exec("<any string>")。 通常的缓解方法是维护一个拒绝列表,列出那些看起来危险的命令。

这行不通。面对拥有无限种表达方式的对手,拒绝列表只能靠猜:

rm -rf /                              →  blocked
r''m -rf /                            →  not blocked
echo cm0gLXJmIC8K | base64 -d | sh    →  not blocked

而且,“看起来危险”甚至不是难点。systemctl restart nginx 是一条再普通不过的命令,但在黑色星期五下午 3 点执行可能造成灾难性后果。

方案

opsgate 不会过滤字符串。它绝不会给 Agent 一个字符串让它填写。

Agent 会获得 25 个类型化工具——service_restart(name)、docker_logs(container)、journal_tail(unit, since, priority)——opsgate 会自行将命令构建为 argv 数组。没有可供注入的 shell,因为系统绝不会根据 Agent 的输入拼装 shell 命令字符串。

// What opsgate does — the name is one argv element, whatever it contains
argv := []string{"systemctl", "restart", in.Name}

// What it never does
argv := []string{"sh", "-c", "systemctl restart " + in.Name}

如果将 nginx; rm -rf / 作为服务名称发送,systemctl 会将其接收为单个字面参数,并因找不到对应单元而失败。不会执行任何其他操作。

之后,每次调用都会通过四道关卡:

flowchart LR
    A[Agent calls a tool] --> B{Policy<br/>mode + allowlist}
    B -->|deny| X[Refused<br/>+ audited]
    B -->|allow| D[Build argv]
    B -->|needs approval| C{Human}
    C -->|deny| X
    C -->|approve| D
    D --> E[Execute]
    E --> F[Audit<br/>hash-chained]
层级作用
结构化工具命令以 argv 形式构建,绝不通过字符串拼接。注入在设计上就不可能发生,而不是靠过滤来防范。
默认拒绝策略提供三种模式——observe(只读)、operate(写入需审批)和 full。支持按主机覆盖策略,并为每种工具设置目标允许列表。
人工审批关卡修改操作会暂停并征求你的同意,同时显示确切命令。需要客户端支持 MCP elicitation;如果客户端无法发起询问,则会拒绝调用,而不会执行。
防篡改审计每次调用(包括每次拒绝)都会追加到采用哈希链的 JSONL 日志中。任何一行被编辑或删除都能被检测到。

默认模式为 observe。开箱即用时,Agent 可以诊断你的服务器,但不会做出任何更改。

亲自试试

别轻信这些说法。以 observe 模式启动 opsgate,并设置 files.allow_paths: [/var/log],然后尝试以下操作——每一项都会被拒绝,并记录拒绝信息:

尝试操作结果
service_status(name: "nginx; touch /tmp/pwned")拒绝——目标名称包含无效字符
file_read(path: "/var/log/../../etc/shadow")拒绝——路径不在允许的路径范围内
file_read(path: "/root/.ssh/id_rsa")拒绝——路径不在允许的路径范围内
file_grep(pattern: "x$(touch /tmp/pwned)")按字面字符串搜索;不会执行任何内容
service_restart(name: "nginx")拒绝——observe 模式不允许修改
设置了 allow_targets 时调用 journal_tail()拒绝——省略单元名称会读取整个日志
shell_exec(command: "id")拒绝——除非选择启用,否则根本不会注册此工具

然后确认没有任何内容被写入:

$ ls /tmp/pwned
ls: cannot access '/tmp/pwned': No such file or directory

$ opsgate audit verify
audit chain OK: 6 records verified in ~/.opsgate/audit.jsonl

不是七项,而是六项:禁用时,shell_exec 根本不是已注册工具,因此调用会在到达安全门之前就被协议拒绝。

如果你发现了确实能够通过的操作?那就是安全漏洞——请报告。

模式

mode: observe   # read-only. Mutating tools are refused outright.
mode: operate   # reads run freely; every write asks you first.  ← recommended
mode: full      # writes run unprompted. Still fully audited.

对生产环境的限制要比其他所有环境都严格:

mode: operate
hosts:
  prod:
    addr: 10.0.0.1
    mode: observe        # prod is read-only even though the default is operate
  staging:
    addr: 10.0.0.2       # inherits operate

限制工具可以操作的对象:

tools:
  service_restart:
    allow_targets: ["nginx", "myapp*"]   # postgresql is not restartable, period
  service_stop:
    enabled: false                       # remove the tool entirely
  shell_exec:
    enabled: true                        # opt in to freeform shell (still approval-gated)

设置 allow_targets 也会让目标成为该工具的必填项:省略目标通常会扩大操作范围,超过任何允许的值——例如,不指定单元名称的 journal_tail 会读取整个日志——因此,无约束的调用会被拒绝,而不是默默放行。

Docker 工具只接受完全匹配的容器名称,不接受 ID 或 ID 前缀,因此像 db* 这样的模式只会匹配你在 docker ps 中看到的内容,不会匹配其他内容。files.allow_paths: [] 会完全关闭文件访问;省略此键则保留默认值。

所有选项请参阅 configs/opsgate.example.yaml。

工具

观察——始终可用

system_info · disk_usage · process_top · listening_ports · service_list · service_status · docker_ps · docker_logs · docker_inspect · docker_stats · compose_ps · journal_tail · journal_errors · file_read · dir_list · file_grep · nginx_test

修改——在 observe 模式下会被拒绝,在 operate 模式下需要经过审批

service_restart · service_start · service_stop · service_reload · docker_restart · docker_start · docker_stop · nginx_reload

选择启用

shell_exec — 任意 Shell 命令。除非启用,否则处于禁用状态;始终需要经过审批。它是为确实难以涵盖的少数需求而设,上面列出的工具则是为了让你很少需要用到它。

nginx_reload 会先运行 nginx -t,如果配置无法解析,就会拒绝重新加载。

值得向你的 Agent 提出的问题
  • “为什么 web1 上的 nginx 会返回 502?” — 关联 service_status、journal_tail、docker_ps
  • “是什么占满了 prod 的磁盘?” — 先运行 disk_usage,再检查允许路径下的 dir_list
  • “昨晚有没有发生崩溃?” — 检查各主机上的 journal_errors
  • “哪些容器处于不健康状态,分别重启了多少次?” — 使用 docker_inspect
  • “比较 staging 和 prod:哪些服务只在其中一个环境运行?” — 在两个环境中使用 service_list

审计日志

每次调用都会追加一行 JSON,其中包含上一条记录的哈希:

{"seq":41,"time":"2026-07-29T11:02:14Z","host":"web1","tool":"service_status","decision":"allow","exit_code":0,…,"prev_hash":"9f2c…","hash":"41ab…"}
{"seq":42,"time":"2026-07-29T11:02:31Z","host":"web1","tool":"file_read","args":{"path":"/root/.ssh/id_rsa"},"decision":"deny","error":"outside the allowed paths",…}
{"seq":43,"time":"2026-07-29T11:03:02Z","host":"web1","tool":"service_restart","phase":"intent","decision":"needs_approval","approved":true,…}
{"seq":44,"time":"2026-07-29T11:03:02Z","host":"web1","tool":"service_restart","phase":"outcome","decision":"needs_approval","approved":true,"exit_code":0,…}

拒绝操作也会记录——之后查到有人试图读取私钥且被拦截,正是你想要的结果。

会更改状态的调用会写入两条记录:在命令运行之前写入 phase: intent,之后写入 phase: outcome。如果无法写入意图记录,opsgate 就会拒绝执行——因此,已经发生的操作绝不会从日志中缺失。

$ opsgate audit verify
audit chain OK: 143 records verified in ~/.opsgate/audit.jsonl

编辑、删除或重新排列任何较早的行,都会导致验证在出错的确切位置失败:

$ opsgate audit verify
opsgate: audit chain INVALID after 41 records: line 42: record tampered (hash mismatch)

伪造字段并插入其中也行不通——验证会进行严格解码:

opsgate: audit chain INVALID after 2 records: line 3: json: unknown field "approved_by"

哈希链无法检测文件末尾被截断的记录,也无法检测具有写入权限的人从头重写日志的情况;如果这很重要,请将日志发送到设备外部或存入仅追加存储(参见威胁模型)。

名为 password 或 token 的参数会在写入前被删去敏感信息;其他所有内容,包括 shell_exec 命令,都会按发送时的原样记录,因此不要在工具参数中放入机密。超长值会被截断为短前缀,并附上其长度和 SHA-256。只存储命令输出的 SHA-256,绝不存储输出本身。

对比

还有其他通过 SSH 提供 MCP 的服务,它们也很有用,只是取舍不同。

自由格式 Shell MCP 服务opsgate
Agent 发送的内容命令字符串固定工具的类型化参数
注入防护对字符串使用正则表达式拒绝列表不会构造 Shell 字符串——仅使用 argv
审批无,或由客户端自动批准每次调用都由服务端审批,并显示确切命令
只读保证依赖约定observe 模式不存在任何可执行变更的代码路径
审计各不相同哈希链式记录,包含拒绝操作,并在执行前记录意图
运行时通常为 Node单个静态 Go 二进制文件,无需运行时

坦诚地说:opsgate 的工具集有限,因此你需要的功能可能尚未涵盖。这正是 shell_exec 和贡献的用武之地。

安全模型

opsgate 可以缩小影响范围。它不是沙箱,也不能让不可信的 Agent 安全地访问生产环境。

它能保证什么

  • Agent 的输入永远不会变成 shell 语法。命令以 argv 形式传递;SSH 传输会对每个元素使用单引号,因此每个参数都会保留为一个字面词。以 - 开头的目标名称会被拒绝,因此无法将其当作命令行选项。
  • observe 模式无法执行修改操作。从只读模式到状态变更命令之间不存在任何代码路径。
  • 文件工具无法超出 files.allow_paths 的范围——路径会在前缀检查前进行清理,因此 /var/log/../../etc/shadow 会被拒绝。
  • SSH 主机密钥会根据 known_hosts 验证。未知密钥会被拒绝,不会在首次使用时默认信任。
  • 每项决策都会被审计,包括拒绝的操作;执行变更操作前也会先记录下来。
  • 一次审批只针对一个提示和一次确切调用(主机、工具、命令),且只能使用一次;客户端无法预先批准从未询问过的调用。
  • opsgate 只会从 --config 或 ~/.opsgate/config.yaml 读取配置,绝不会从启动目录读取,因此仓库无法附带会放宽策略的配置。

它无法保证什么

  • 工具以你的 SSH 用户权限运行。如果该用户是 root,那么经批准的调用就会以 root 权限运行。请为 opsgate 配置一个权限最小化的专用用户。
  • 启用并批准后,shell_exec 就能执行任意代码。这正是它的用途。
  • full 模式会移除人工把关。只有在你接受这一点时才使用它。
  • 遭受提示注入的 Agent 仍可能使用看似合理的参数调用允许的工具。策略负责限制损害——请严格限制 allow_targets。
  • 路径检查采用词法方式:允许目录中的符号链接若指向该目录之外,仍会被跟随。
  • 审批是否可靠,取决于显示提示的 MCP 客户端是否诚实。会自行回答“接受”的客户端会使审批失效;请使用你信任的客户端。

有关威胁、控制措施和限制的详细信息,请参阅 docs/threat-model.md。

为什么要开发它

我管理自己的基础设施,也希望让 Claude 能访问它。所有现有方案都让我在“毫无用处”和“这是 root shell,祝你好运”之间做选择。opsgate 则取其中间路线:Agent 获得实际能力,我保留否决权和操作记录。

贡献

我们真诚欢迎贡献;该架构也让添加新工具变得简单——大多数只需约 20 行代码。请阅读 CONTRIBUTING.md,了解唯一重要的规则(命令必须使用 argv,绝不能拼接字符串)。

适合初次贡献的内容

  • 更多结构化工具——Kubernetes(kubectl get/describe/logs)、PostgreSQL 和 MySQL 状态、Caddy、Traefik、ufw/iptables 状态、证书过期时间、apt/dnf 待安装更新
  • 策略原语——时间窗口(“工作时间内不重启”)、速率限制、每种工具的并发上限
  • 审计接收端——syslog、仅追加的 S3、webhook
  • 客户端兼容性报告——告诉我们审批提示在你的 MCP 客户端中的显示效果
go test -race ./...
go vet ./...
gofmt -l .          # must print nothing

如果 opsgate 对你有用,点个 ⭐ 就能帮助其他管理自有基础设施的人找到它。

许可证

MIT——请参阅 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

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

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