
opsgate
io.github.polymatx由 GitHub 账号 polymatx 发布v0.1.3更新于 Oct 26, 2026
The safety gate between AI agents and your servers: typed, policy-checked, audited ops tools.
概览
带策略校验的 MCP 服务器,让 AI 代理通过类型化、可审计的工具检查并操作 Linux 服务器,而无需 shell。
- 可以做什么
- 检查主机:系统信息、磁盘占用、进程排行、监听端口、服务状态、journal 日志、文件读取与 nginx 配置测试
- 检查 Docker:列出容器、读取日志、查看健康状态与重启次数、资源统计与 compose 状态
- 变更状态:重启、启动、停止或重载服务、容器与 nginx;observe 模式下直接拒绝,operate 模式下需人工批准
- 仅在显式启用时提供自由 shell,且始终需要批准
- 把每次调用(包括被拒绝的调用)写入可校验的哈希链审计日志
- 适用场景
- 适合希望让代理通过 SSH 或本机诊断并处理真实服务器、但不愿交出 root shell 的场景。默认只读排查,需要重启或重载时逐次人工确认。
- 运行要求
- 以本地进程方式运行(Go 二进制或已发布的容器镜像),通过 stdio 连接;沙箱类客户端需改用 HTTP。需要配置文件(通常由初始化命令写入用户主目录),远程主机还需 SSH 访问与密钥。HTTP 模式需在配置中设置认证令牌。
安装
在 SourceWeft 中
- 打开 控制台中的 opsgate,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
opsgate
The safety gate between AI agents and your servers.
An MCP server that lets Claude, Cursor, or any AI agent operate real infrastructure — without handing it a root shell.
[CI] [Go Report Card] [Go Reference] [License: MIT] [Go 1.26+] [MCP]
What it looks like · 30-second try · Break it yourself · Tools · Security model · Contributing
[An agent's calls to opsgate: a shell injection, a path traversal and a restart in observe mode are all refused, nothing runs, and each attempt is in the hash-chained audit log]What it looks like
You ask a question. The agent investigates with read-only tools and answers.
You: Something's listening on port 8000 on my server and I don't remember what. Check it.
Claude: Port 8000 is a bare
python3process (pid 237608) bound to localhost only, so it isn't reachable from outside. It's not managed by systemd — no unit owns it — which means it won't survive a reboot. It's most likely something started by hand in a shell.
Now something that changes state. In operate mode the agent cannot just do it:
You: OK, nginx config changed. Reload it.
You click Deny, and nothing happens — the agent is told the operator refused. Approve, and it runs and lands in the audit log. Either way you saw the exact command first.
(Tool output above is real output from a live server; the prompt is rendered by your MCP client, so it looks however that client draws it.)
Try it in 30 seconds (no server needed)
opsgate runs with no config file at all — local machine, read-only:
That's it. No config, no SSH, and it defaults to observe — so it can read your disk usage
and journal, and it will refuse to change anything:
Point your client at it and ask "what's using disk space on this machine?" When you're
ready for real hosts, opsgate init writes a starter config to ~/.opsgate/config.yaml.
Which clients can reach it
opsgate runs on your machine (or on the server itself) and holds your SSH key, so the transport you need depends on where your client actually executes:
If your client reports it has no access to your servers, it is almost certainly one of the sandboxed kind. Serve over HTTP instead of stdio.
Before publishing that endpoint, read the warning there: it puts an infrastructure-management API on whatever network you expose it to.
Wire it up to your client
Claude Code
As a plugin: the MCP server, a server-ops skill that teaches the agent to diagnose before
it acts and to treat refusals as final, and a read-only /opsgate:health-check [host] command.
The plugin runs opsgate serve, which reads ~/.opsgate/config.yaml (opsgate init writes a
starter one). It never reads a config from the project you have open.
Or add just the MCP server:
Claude Desktop / Cursor (claude_desktop_config.json or .cursor/mcp.json)
Docker (ghcr.io/polymatx/opsgate)
For hosts reached over SSH. Inside a container, local is the container itself.
The image is also how opsgate is listed in the MCP Registry
as io.github.polymatx/opsgate.
Any client, over HTTP (needed for cloud/sandboxed clients)
The tidiest deployment is to run opsgate on the server it manages — then it needs no SSH
key at all (default_host: local) — behind a TLS-terminating reverse proxy:
opsgate refuses to serve HTTP on a non-loopback address unless http.auth_token is set.
[!WARNING] Exposing this endpoint puts an infrastructure-management API on that network, and the bearer token becomes the only thing protecting it. If you must publish it: keep
mode: observe, generate 32+ bytes of randomness for the token, require TLS, and add an IP allowlist at the proxy. Binding it to a VPN interface (WireGuard, Tailscale) instead of the public internet is strictly safer — your own devices reach it, nobody else can.Note also that
operatemode expects a human who can answer an approval prompt. An unattended HTTP deployment usually meansobserve.
The problem this solves
Letting an AI agent run your servers today means giving it exec("<any string>") over SSH.
The usual mitigation is a denylist of commands that look dangerous.
That does not work. Denylists are guesswork against an adversary with infinite phrasings:
And "dangerous-looking" isn't even the hard part. systemctl restart nginx is a perfectly
ordinary command that is catastrophic at 3pm on Black Friday.
The approach
opsgate does not filter strings. It never gives the agent a string to fill in.
The agent gets 25 typed tools — service_restart(name), docker_logs(container),
journal_tail(unit, since, priority) — and opsgate builds the command itself as an argv
array. There is no shell to inject into, because no shell string is ever assembled from
agent input.
Send nginx; rm -rf / as the service name and systemctl receives it as a single literal
argument and fails as an unknown unit. Nothing else runs.
Every call then passes through four gates:
The default mode is observe. Out of the box, an agent can diagnose your servers and
change nothing.
Break it yourself
Don't take the claims on faith. Start opsgate in observe mode with
files.allow_paths: [/var/log] and try these — every one is refused, and the refusal is
logged:
Then confirm nothing landed:
(Six, not seven: shell_exec isn't a registered tool at all when it's disabled, so that
call is rejected by the protocol before it reaches a gate.)
Found something that does get through? That's a security bug — please report it.
Modes
Pin production tighter than everything else:
Restrict what a tool may touch:
Setting allow_targets also makes the target mandatory for that tool: omitting it is
usually broader than any allowed value — journal_tail with no unit reads the whole
journal — so an unconstrained call is refused rather than silently permitted.
Docker tools accept only exact container names, never IDs or ID prefixes, so a pattern
like db* matches what you see in docker ps and nothing else. files.allow_paths: []
turns file access off entirely; leaving the key out keeps the defaults.
See configs/opsgate.example.yaml for every option.
Tools
Observe — always available
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
Mutate — refused in observe, approval-gated in operate
service_restart · service_start · service_stop · service_reload ·
docker_restart · docker_start · docker_stop · nginx_reload
Opt-in
shell_exec — freeform shell. Disabled unless you enable it; always approval-gated. It
exists for the genuine long tail, and everything above exists so you rarely need it.
nginx_reload runs nginx -t first and refuses to reload a config that does not parse.
Things worth asking your agent
- "Why is nginx throwing 502s on web1?" — correlates
service_status,journal_tail,docker_ps - "What filled up the disk on prod?" —
disk_usagethendir_listunder allowed paths - "Did anything crash overnight?" —
journal_errorsacross hosts - "Which containers are unhealthy and how many times have they restarted?" —
docker_inspect - "Compare staging and prod: what's running on one but not the other?" —
service_liston both
The audit log
Every call appends one JSON line embedding the hash of the previous record:
Refusals are recorded too — a blocked attempt to read a private key is exactly what you want to find later.
A state-changing call writes two records: phase: intent before the command runs and
phase: outcome after. If the intent record cannot be written, opsgate refuses to act — so
an action that happened can never be missing from the log.
Edit, delete, or reorder any earlier line and verification fails at exactly that point:
Splicing in a forged field doesn't work either — verification decodes strictly:
The chain cannot show records cut off the end of the file, or a log rewritten from scratch by someone with write access; ship it off the box or to append-only storage if that matters (see the threat model).
Arguments named like password or token are redacted before they are written; everything
else, including a shell_exec command, is logged as sent, so keep secrets out of tool
arguments. Very long values are cut to a short prefix plus their length and SHA-256. Only
the SHA-256 of command output is stored — never the output itself.
How this compares
Other SSH-over-MCP servers exist and are useful; they make a different trade.
The honest trade: opsgate's tool set is finite, so something you need may not be covered
yet. That's what shell_exec and contributions are for.
Security model
opsgate reduces blast radius. It is not a sandbox, and it does not make an untrusted agent safe to point at production.
What it guarantees
- Agent input never becomes shell syntax. Commands are
argv; the SSH transport single-quotes every element, so each argument stays one literal word. Target names that start with-are refused, so they cannot turn into command-line options. observemode cannot mutate. There is no code path from a read-only mode to a state-changing command.- File tools cannot escape
files.allow_paths— paths are cleaned before the prefix check, so/var/log/../../etc/shadowis refused. - SSH host keys are verified against
known_hosts. Unknown keys are refused, never trusted on first use. - Every decision is audited, including refusals, and a mutating action is recorded before it runs.
- An approval answers one prompt for one exact call (host, tool, command) and is used once; a client cannot pre-approve a call it was never asked about.
- opsgate reads its config only from
--configor~/.opsgate/config.yaml, never from the directory it starts in, so a repository cannot ship a config that widens the policy.
What it does not
- Tools run with the privileges of your SSH user. If that is root, an approved call is a root call. Give opsgate its own least-privilege user.
shell_exec, once enabled and approved, is arbitrary code execution. That is its purpose.fullmode removes the human. Use it only where you accept that.- A prompt-injected agent can still call allowed tools with plausible arguments. The
policy is what bounds the damage — keep
allow_targetstight. - The path check is lexical: a symlink inside an allowed directory pointing outside it will be followed.
- Approval is only as honest as the MCP client that shows the prompt. A client that answers "accept" on its own defeats it; use a client you trust.
See docs/threat-model.md for adversaries, controls, and limits in detail.
Why this exists
I run my own infrastructure and I wanted to point Claude at it. Every existing option asked me to choose between "useless" and "here is a root shell, good luck." opsgate is the middle: the agent gets real capability, I keep the veto and the receipts.
Contributing
Contributions are genuinely welcome, and the architecture makes new tools easy — most are ~20 lines. Read CONTRIBUTING.md for the one rule that matters (commands are argv, never concatenated strings).
Good first contributions
- More structured tools — Kubernetes (
kubectl get/describe/logs), PostgreSQL and MySQL status, Caddy, Traefik,ufw/iptablesstatus, certificate expiry,apt/dnfpending updates - Policy primitives — time windows ("no restarts during business hours"), rate limits, per-tool concurrency caps
- Audit sinks — syslog, append-only S3, webhook
- Client compatibility reports — tell us how the approval prompt renders in your MCP client
If opsgate is useful to you, a ⭐ helps other people running their own infrastructure find it.
License
MIT — see LICENSE.
来源:README.md,提交 2f33e86
工具
0版本历史
2- v0.1.3最新Oct 18, 2026
- v0.1.2Oct 18, 2026
评分
已满足适用检查项的 50%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
- 近 30 天内可连通25 分
请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。
- 已声明认证方式10 分
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
- 已列出工具15 分
请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。
- 描述必需15 分
- 图标10 分
- 文档15 分
- 许可证10 分