关于 OpenWrt MCP
OpenWrt MCP 运行在标准 OpenWrt 路由器上,供 Claude Code 等 MCP 客户端检查和管理路由器设置。它结合默认拒绝授权、审计日志、秘密信息遮蔽,以及带回滚保护的配置更改。
主要功能
- 01检查路由器健康、日志和网络状态
- 02应用带检查与回滚的 UCI 更改
- 03管理软件包、服务并查看防火墙
- 04管理 WireGuard 对端
- 05按客户端授权并记录审计日志
适用场景
- 01查看路由器健康状况和安全发现
- 02暂存配置更改并确认或回滚
- 03检查网络客户端和防火墙规则
- 04管理软件包与 WireGuard 对端
MCP 相关
运行在路由器上的服务通过 SSH 上的 stdio 或 loopback HTTP 向 MCP 客户端提供 23 个工具。工具访问由授权控制,可能发起出站连接的操作会在 MCP 注释中标明。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
openwrt-mcp——适用于 OpenWrt 24.10 和 25.12 的 MCP 服务
一个运行于 OpenWrt 路由器上的 MCP 服务,因此 Claude Code(或任何 MCP 客户端)都可以检查和更改路由器——并由默认拒绝策略、 审计日志以及配置更改自动回滚机制提供保护。
本地且私密。 服务本身不会建立任何出站连接,也不会发送遥测数据;它只监听
环回地址和仅限 root 访问的套接字,并通过你设置的 SSH 会话与客户端通信。能够让路由器
本身主动连接外部的工具(net_diag、
带 refresh 的 pkg_query、pkg_change、sysupgrade check、uci_apply 探测、
ubus_call、exec)会在其 MCP 注释(openWorldHint)中说明这一点,且每个工具都需要
获得你的授权。工具输出中的机密值默认会被屏蔽;除非你提出要求,否则新的 WireGuard 密钥不会进入对话。
占用空间。 一个无依赖的静态二进制文件:适用于路由器架构的文件大小为 9.1 至 11.3 MB (arm64 上为 9.6 MB,在发布归档中压缩后约为 3.7 MB);在 GL-MT6000 上刚启动后常驻内存约 10 MB;23 个工具的描述约占 22 KB。每个发布文件都经过校验和验证,并附带构建证明。
基于 GlassOnTin/openwrt-mcp,作者 Ian Williams,该项目面向 GL.iNet 固件 4.x(OpenWrt 21.02,
opkg)。这是一个移植版本,面向 原版 OpenWrt 25.12(apk、fw4/nftables、procd、netifd、LuCI JS)。安全核心来自 上游;来源详情请参阅致谢。
要求
- **路由器:**原版 OpenWrt 25.12(
apk)或 24.10(opkg),两者均使用 fw4/nftables。路由器无需预先安装任何内容。软件包工具会根据路由器上的二进制文件选择包管理器(先检查apk,再检查opkg);system_status会说明找到的是哪种包管理器和防火墙。- 24.10 的差异:
pkg_query没有policy和audit(opkg 两者都不支持;工具会说明这一点)。pkg_change使用opkg --noaction进行模拟。新软件包的默认配置会以<file>-opkg而非<file>.apk-new的形式出现,pkg_config_diff/pkg_config_resolve可处理这两种形式(旧副本会保留为.pre-opkg-new;对于/etc/config/NAME,则保留为隐藏文件.NAME.pre-opkg-new,reload_config会跳过该文件)。添加或移除 WireGuard 对等节点时,会针对该接口重新运行ifup(24.10 的处理程序无法就地同步对等节点),因此每条隧道的会话都会短暂重启。world会列出 opkg 标记为“用户安装”的软件包。system_status mode=doctor会报告opkg-new-pending;mode=audit会跳过软件包审计并说明原因。 - **fw3(OpenWrt 21.02 及更早版本,或基于这些版本构建的固件):**尽力支持。
firewall_show支持ruleset(iptables-save,IPv4)和rendered(fw3 -q print);不支持check、table和chain;应用防火墙配置前不会进行预检查。 - **测试环境:**在 GL-MT6000(硬件)上使用 25.12.5 测试;单元测试会在模拟环境中运行两种包管理器;CI 会在 24.10 和 25.12 的
openwrt/rootfs容器中运行软件包和 uci 工具(容器中没有 ubus 或 netifd,因此未覆盖网络、无线和防火墙路径)。基于 OpenWrt 衍生的固件(GL.iNet、ImmortalWrt)尽力支持。
- 24.10 的差异:
- **你的 PC:**OpenSSH(Windows 10 及更高版本、macOS 和 Linux 均已提供)。从发行版本安装无需其他内容。从源代码构建需要 Go 1.26+、
ssh和tar(Linux、macOS 或 Windows 上的 Git Bash)。 - **适用于任意 CPU 架构:**OpenWrt 和 Go 都支持:发行版本为每种架构提供了二进制文件(arm64、armv5/v7、mips/mipsle softfloat、mips64/mips64le、x86、x86-64、riscv64);
install.sh会读取路由器的DISTRIB_ARCH并选择对应版本,或者在从源代码构建时进行交叉编译。
已在 GL.iNet GL-MT6000(mediatek/filogic,aarch64_cortex-a53)上使用 OpenWrt 25.12.5 测试。
代码中没有任何内容专门针对该板卡。
与其他 OpenWrt MCP 服务的区别
根据各项目在 2026-10-07 发布的 README;“未说明”表示 README 中没有提及。欢迎提出更正。
| 本项目 | openwrt_ssh_mcp | paulomac1000/openwrt-mcp | openwrt-luci-mcp | |
|---|---|---|---|---|
| 运行位置 | 路由器 | PC 上,通过 Docker 运行 | PC 上,通过 Docker 或 Python 运行 | PC 上,通过 Node.js 运行 |
| 连接路由器的方式 | 通过 SSH 连接到绑定密钥的强制命令,或通过环回 HTTP | SSH | SSH | LuCI 的 HTTP /ubus |
| 是否更改配置 | 是,通过一个会先检查并启用回滚的工具 | 是,包括软件包和固件刷写 | 只读,除非设置 ENABLE_WRITE_OPERATIONS=1 | 否 |
| 错误更改后是否自动回滚 | 是,且重启后仍有效 | 未说明 | 未说明 | 不适用 |
| 访问控制和审计 | 按客户端授权,支持范围、有效期和速率限制;提供审计日志,可选 TOTP | 命令白名单,审计日志 | 审计日志 | 隐藏机密信息,无审计日志 |
| 包管理器 | apk(25.12)或 opkg(24.10) | opkg | opkg | 列出已安装和可用的软件包 |
| 固件刷写 | 从不 | 是 | 未说明 | 否 |
| 路由器上的依赖 | 二进制文件 | SSH | SSH | uhttpd-mod-ubus、rpcd |
与上游版本的变化
| 上游 0.5(21.02 / GL.iNet) | 此移植版(25.12) | |
|---|---|---|
| 打包 | 供 opkg 使用的 .ipk | install.sh 文件安装 + keep.d(25.12 的 apk 没有 mkpkg;见下文) |
| Web UI | GL.iNet oui-httpd 页面 | LuCI 页面,Services -> MCP Server:查看状态,以及批准或拒绝请求 |
| 传输 | 通过 ssh -L 隧道使用 HTTP | 相同,另加通过 SSH 的 stdio,使用绑定密钥的强制命令——无需保持隧道连接 |
| 回滚快照 | /tmp(重启后丢失) | 保存在闪存中;未确认的更改会在下次启动时回滚,包括断电重启后 |
uci_apply | 设置 / 创建 / 删除 | + add_list、del_list、set_list、dry_run,显示暂存的 uci changes,严格验证名称。试运行结束时会给出建议:warnings from this change。 |
| 重新加载 | ubus call uci reload_config(异步) | /sbin/reload_config;若没有校验和,还会显式发送 config.change 事件(启动后的首次运行) |
| WireGuard | GL wireguard_server / gl_ddns | 标准的 network.<iface> + wireguard_<iface> 对等端,来自 ddns 的 DDNS、CGNAT 警告、PSK、列出和移除 |
| 软件包 | -- | pkg_query、pkg_change(先模拟),通过设置级差异审查 .apk-new |
| 防火墙 | -- | firewall_show:nft 规则集、fw4 print、fw4 check |
| 服务 | -- | 通过 rpcd rc 使用 service_list / service_control |
| 范围 glob | [0] 曾是一个字符类 | 方括号按字面处理:firewall.@rule[3].* 即表示其字面含义 |
| 预设 | -- | `openwrt-mcp allow @readonly |
工具
| 工具 | 范围 | |
|---|---|---|
system_status | 工具 | 型号、版本、运行时间、负载、内存、存储、温度、conntrack、每个接口(状态、地址、默认路由、错误)、无线电/SSID,以及待处理事项。请先调用。mode=doctor 返回按优先级排列的健康问题(无线电关闭、接口没有地址、已启用的服务已停止、conntrack 和存储即将满、待处理的 .apk-new、NTP 或时钟未设置、已安装的内核比正在运行的内核更新)。mode=audit 返回按优先级排列的安全问题(防火墙允许从互联网进入的内容、SSH 和 LuCI 的暴露情况、密码登录、UPnP、WPS、开放或安全性较弱的 Wi-Fi、root 密码为空、软件包文件已修改、WireGuard 对等节点过期)。两者仅供参考,每条问题都会指出下一项只读调用和一个 OpenWrt wiki 页面,并列出无法检查的内容。它们的范围相互独立:openwrt-mcp allow <client> system_status doctor。 |
logread | 工具 | 在行数限制之前筛选整个日志缓冲区(子字符串、RE2、最近 N 分钟);offset 从最新行开始向前翻页。mode=summary 会将日志折叠为不同的消息(数字、地址和 ID 替换为占位符),并提供数量、首次和最近时间以及最高严重性。baseline=save 返回一个令牌;baseline=<token> 仅显示当时日志中没有的消息。基线保存的是指纹,而不是日志文本,并驻留在内存中。 |
network_clients | 工具 | DHCP 租约 + 静态主机 + 邻居表 + 每个 AP 的关联列表,按 MAC 合并:名称、IP、SSID、信号、速率、连接时间、每个客户端占用其无线电空中时间的比例(来自 hostapd)、租约;每页 100 行,使用 offset 查看下一页。 |
firewall_show | 工具 | nft list ruleset、fw4 print、fw4 check、单个表或链。 |
net_diag | <action>[.<target>] | ping / traceroute / nslookup(可指定接口)、路由、策略规则、邻居。wifi_survey 按无线电报告其所在信道的繁忙程度(繁忙时间、接收和发送空中时间、噪声);不会测量其他信道,因此不会推荐信道。traffic 会在几秒内采样接口速率,并按来源排列 conntrack 表。usage 读取 nlbwmon 按设备统计的计费周期总量(默认按月)。这三项都只读取数据。 |
uci_get | <config>[.<section>[.<option>]];按名称搜索使用 refs | uci show,可选择使用稳定的 cfgXXXXXX ID。末尾显示配置的修订版本。history=list / diff:<id> 显示每次确认更改之前保留的版本。refs=NAME 可在各配置中查找定义和使用某个名称的位置。 |
uci_apply | 每项更改使用 <config>.<section>[.<option>];还原使用 <config>;每项探测使用 probe.<kind>.<target> | 暂存、检查、提交、重新加载,并启用回滚保护。dry_run 会准确显示将发生的更改,以及服务自身检查器的检查结果。若配置在此期间发生变化,expected_revisions 会拒绝操作;probe 会在之后检查路由器;restore=<id> 会还原到过去的版本。提交后会重新读取配置;如果路由器没有保留某项更改,则返回 NOT_APPLIED,且不会重新加载。 |
uci_confirm / uci_rollback | 工具 | 将待处理的更改永久生效 / 立即撤销。 |
service_list / service_control | 工具,<service>.detail / <service>.<action> | procd 服务;service_list detail=NAME 返回已安装附加组件自身的状态(tailscale、AdGuard Home、podkop;普通列表会列出此处已安装的服务);拒绝停止或禁用 dropbear、network、rpcd 或 openwrt-mcp。service_control 会等待状态稳定(wait),并说明状态是否未稳定;如果最终状态与操作相矛盾,则返回 NOT_APPLIED。 |
pkg_query | 工具 | 已安装、可升级、搜索、信息、文件、所有者、策略、apk audit、world;每页 200 行,使用 offset 查看下一页。 |
pkg_change | <action>.<pkg> / upgrade | apk add/del/upgrade;除非 commit=true,否则仅模拟操作;报告新建的 .apk-new 文件;之后检查 /etc/apk/world。 |
pkg_config_diff | 工具 | 将每个 .apk-new 与当前文件进行差异对比——对于 /etc/config/*,按设置进行对比(uci show),因此引号/缩进差异不会造成干扰。 |
pkg_config_resolve | 当前路径 | keep_current(丢弃新的默认配置)或 use_new(安装新配置;UCI 配置启用回滚保护)。 |
sysupgrade | <action> | list(保留的文件)、test(验证 /tmp 中的映像)、check(owut)、backup(模式 0600;删除此工具创建的上一个归档)。绝不会刷写固件。 |
wg_list_clients | 工具 | 对等节点的握手时长、端点、流量、配置/内核不匹配情况、重复名称。 |
wg_new_client | wireguard.<iface>;reveal=true 时使用 wireguard.<iface>.reveal | 密钥对、下一个可用地址、保存到网络配置中的对等节点;uci_confirm 会保留此更改。私钥和配置会写入仅 root 可访问的文件;使用 openwrt-mcp wg-show <name> 获取(配置和 QR 码会显示在你自己的终端中)。reveal=true 则会在结果中返回它们。 |
wg_remove_client | wireguard.<iface>.<name> | 按名称、密钥或区段从配置和接口中移除;启用回滚保护(uci_confirm 会保留此更改)。如果某个对等节点在最近 3 分钟内连接过,或是当前会话自己的对等节点,则拒绝移除,除非 force=true。 |
ubus_list | (不受门控) | 发现:所有对象、方法和参数签名。 |
ubus_call | <object>.<method> | 总线上其他任何内容。超过 8 KB 的回复会删减较长数组。 |
exec | argv[0] | 单个程序,不使用 shell。对 sh、find、awk、env、ssh 等的授权相当于 root shell:没有 --shell-equivalent 时,allow 会拒绝授权。 |
mfa_unlock | (不受门控) | 打开 MFA 门控工具的 TOTP 时间窗口。 |
| 提示词。 显示 MCP 提示词的客户端(通常作为斜杠命令)可获取五个操作指南: | ||
router-health、who-is-online、secure-my-router、wifi-doctor 和 upgrade-plan。每个指南都用几行文字列出要调用的工具,并以报告或提案结尾,绝不执行任何更改。它们只使用 @readonly 预设授予的权限,因此只读客户端可以按指南操作到底,而且只有在使用时才会产生费用。列出或获取指南不会在路由器上运行任何操作,因此不适用任何策略,也不会留下审计记录。 |
警告、引用、对等端和附加组件
试运行中的警告。 uci_apply dry_run 会在末尾显示 warnings from this change:列出此次更改会新增、但路由器当前尚不存在的问题,按与审计相同的方式排序,每项都附有后续步骤和 OpenWrt wiki 页面。这些只是建议,不会拒绝操作。问题包括:向互联网开放端口或端口转发、将 WAN 区域的输入或转发设为 ACCEPT、启用 SSH 密码登录、SSID 未加密、某项更改移除或拼错名称但其他区段仍在使用该名称(ref-removed、ref-unknown;大小写不匹配时会提示“是否想找”),以及关闭你当前会话所连接的 Wi-Fi 网络(radio-in-use)。更改前就已存在的问题不会再次报告。实际应用更改时不会重复这些警告,因此请先运行试运行。
管理路径跟随你的会话。 除 LAN、其网桥、SSH 监听器以及 SSH 所用的区域和规则外,你自己的 SSH 连接所经由的接口也属于管理路径(守护进程会使用 ip route get 解析客户端地址),因此,对你当前连接所经的 WireGuard 隧道进行更改时,也需要探测或设置 force=true,与更改 LAN 一样。
重命名或删除接口、区域或设备之前。 uci_get refs=lan 会列出定义该名称的对象(网络接口、防火墙区域、设备、无线电、mwan3 成员或策略),以及使用该名称的所有引用字段:区域 network、规则/转发/重定向的 src 和 dest、dhcp interface、无线 network 和 device、网络 device、ports、route.interface、以及安装后可用的 sqm 和 mwan3。“defined as: nothing” 表示没有区段使用该名称(可能是拼写错误或大小写不匹配)。这里只会读取这些字段,因此不能用此搜索查找键。它需要 refs 作用域,@readonly 和 @operator 都包含该作用域;config=firewall 可将范围缩小到单个配置。
添加和移除 WireGuard 对等端属于常规的、带回滚保护的更改。 对等端会写入 /etc/config/network,并由 netifd 加载,因此其他对等端的会话会保持连接。两个工具都会等待对等端出现在运行中的接口上(或从中移除),并说明验证结果。只有执行 uci_confirm 后更改才会永久生效;否则,超时或重启后,对等端会再次被移除,新客户端的密钥文件也会被删除。一次只能有一项更改等待确认。若要移除你自己的会话所经由的对等端,需要设置 force=true。
附加组件状态。 service_list detail=NAME 会读取已安装的附加组件(其 init 脚本存在),并以只读方式输出一组固定字段:它绝不会回显配置文件或命令的原始输出,因为其中可能包含登录哈希、代理链接、节点密钥和令牌。授权时需要该服务的作用域(tailscale.detail,或像 @readonly 中那样使用 *);普通列表不需要任何作用域。由他人设置的文本(例如 tailnet 对等端的主机名)会被截断、清理并标记为不可信。
| 附加组件 | 报告内容 | 已验证 |
|---|---|---|
| tailscale | 状态、tailnet DNS 后缀、本节点地址、通告的路由、出口节点、运行状况、对等端(在线状态、直连或中继、流量) | GL-MT6000,25.12 |
| AdGuard Home | 运行状态、监听地址、防护和过滤开关、上游(仅方案和主机)、引导服务器、过滤列表和规则数量、dnsmasq 是否转发至该服务 | GL-MT6000,25.12 |
| podkop | 版本、sing-box 运行状态、nft 表、dnsmasq 路径、设置、每个区段一行(类型、接口、社区列表、动态列表大小) | GL-MT6000,25.12 |
有意不读取:AdGuard Home 的 Web API(需要 UI 登录)、podkop 的 proxy_string、选择器和出站设置,以及用户的域名和子网列表。
安装
在路由器上从发布版本安装(除默认的 25.12 镜像外,无需其他内容):
wget -O /tmp/install-router.sh https://github.com/stanislav-testhub/openwrt-mcp/releases/latest/download/install-router.sh
sh /tmp/install-router.sh
它会根据路由器的 DISTRIB_ARCH 选择归档文件,根据发布版本的
SHA256SUMS 校验文件(校验不匹配时拒绝安装),然后进行安装。设置 VERSION=v1.2.0 可选择最新版本以外的发布版本。每个发布文件还附带构建证明:
gh attestation verify <file> --repo stanislav-testhub/openwrt-mcp。
在 PC 上从源代码构建(需要 Go、ssh 和 tar;Windows 上可使用 Git Bash):
git clone https://github.com/stanislav-testhub/openwrt-mcp
cd openwrt-mcp
[email protected] SSH_PORT=22 SSH_KEY=~/.ssh/id_ed25519 ./install.sh install
ROUTER、SSH_PORT 和 SSH_KEY 是路由器的 root 登录信息(默认值:[email protected]、
22,以及 ssh 的默认设置)。./install.sh install --release [vX.Y.Z] 会跳过构建,让路由器改为获取经过验证的发布版本;未找到 Go 工具链时也会采用这种方式。
无论哪种方式,都会在路由器上运行同一个安装程序。
- 它会安装:
/usr/bin/openwrt-mcp;- init 脚本;
- 默认配置,不会覆盖现有配置;
keep.d条目;- LuCI 页面。
- 随后启用并启动服务。
- 如果
uci_apply正在等待确认,它会拒绝重启守护进程,因为这会导致配置回滚;除非设置FORCE=1。 - 它会先检查二进制文件是否能放入 overlay。
./install.sh uninstall [--purge] 可撤销安装。
从 LuCI 批准
安装后,LuCI 中的 Services -> MCP Server 会显示守护进程(运行或已停止、版本、HTTP 监听器、stdio 套接字)、待处理的 uci_apply 及其自动回滚截止时间、已配对的 HTTP 客户端、生效中的策略和审计日志。你也可以在此处回复 Agent,因此无需打开 shell 粘贴 allow 命令。
- Agent 调用了一项尚未获授权的工具。该调用会被拒绝,并记录一条请求:客户端、工具、未通过检查的确切范围以及经过脱敏的参数。这些信息均由守护进程生成,不包含来自 Agent 的文本。
- 请求会显示在 MCP 页面顶部,也会显示在 Status -> Overview 中的小组件内;仅在有待处理请求时才会显示该组件。
- Allow 会添加授权,Deny 会拒绝请求。默认授权范围最窄(仅限此工具在此确切范围内使用,时长 1 小时)。通过下拉选项可扩大范围(该范围所属的类别、所有范围、
@operator预设),并将时长设为 15 分钟至 7 天。对于请求 root-shell 工具的请求,页面会显示警告,并要求再次确认。 - 设置
option request_wait '300'(单位为秒,范围 0 至 600,默认值为 300;0 表示立即拒绝)后,被拒绝的调用会进入等待状态;只要有授权覆盖该调用,它就会自动继续,Agent 无需重试。若未设置此选项,或等待超时,Agent 会收到带有请求 ID 的POLICY_DENIED;获批后重试即可。allow命令仍会显示出来,CLI 也会像按钮一样释放等待中的调用。
请求会去重,数量有限(每个客户端最多 5 条未处理请求,总计最多 50 条),并在 request_ttl 指定的时长后过期(默认 10 分钟)。请求保存在 RAM 中,因此重启后会清除。只有读取 ACL 权限的 LuCI 用户可以查看请求,但看不到按钮;写入操作需要 luci-app-openwrt-mcp-write。
其他标签页:Permissions(预设、按组排列并带有风险徽章的工具、Revoke)、Activity(带筛选器的审计日志)、Settings(请求等待时间和 TTL、可选通道、二次验证状态)。
允许全部权限。 Permissions 标签页底部有一个红色区域,可授予客户端对所有范围内所有工具的使用权限,包括 exec:也就是 root shell。最长为 24 小时(不提供“永不”选项),需要勾选复选框并输入客户端名称;已启用二次验证的客户端无法使用此选项。启用期间,两个页面都会显示红色横幅,并提供 End now 按钮。通过此方式授权的调用会在审计日志中标记为 "via":"permit-all"。对应的 CLI 命令为 openwrt-mcp allow <client> @all 1h --shell-equivalent。建议逐条批准请求。
可选通道安装后默认关闭;每个通道都可通过 Settings 中的开关启用,旁边会显示相关风险提示,每次切换都会记入审计日志。
| 通道 | 选项 | 功能 | 可能造成的危害 |
|---|---|---|---|
| 从 LuCI 配对 | pairing | 一个 客户端 选项卡:粘贴 openwrt-mcp connect 输出的密钥,为客户端命名,并选择一个起始预设(默认为只读)。写入与 authorize-key 相同的强制命令行。 | 任何拥有 LuCI 写入权限的人都可以添加一个通过 SSH 访问路由器的密钥。 |
| 推送钩子 | push_hook | 每个新请求都会触发 ubus 事件 openwrt-mcp.request,并运行 /etc/openwrt-mcp/hooks.d/ 中的可执行文件,将已脱敏的请求作为 JSON 写入标准输入(限时 10 秒;跳过组或全局可写、不可执行以及点文件)。适用于 Telegram、ntfy 或 Home Assistant。通知不会批准请求;它会指向 LuCI。 | 请求详情会离开路由器,而且钩子以 root 身份运行。 |
| 在聊天中批准 | chat_approve、list chat_approve_client | 支持协商 elicitation 的客户端也会在自己的提示词中显示请求。仅限确切范围,可授权 15 分钟、1 小时或 8 小时,绝不适用于 root shell 工具或 全部允许。 | 客户端会转达你的回答。会自动回答提示词的客户端或 Agent 可能会自行授权。 |
Agent 无法做什么。 LuCI 管理员本身已是 root,因此按钮不会赋予任何人更多权限。必须防止的是 Agent 扩大自己的授权,因此内置的 拒绝底线 无论授权内容如何(包括 @operator 和 @all)都会拒绝:对对象 openwrt-mcp、session、file 和 rpc-sys 执行 ubus_call,以及写入 openwrt-mcp 和 rpcd 配置的 uci 操作;对二进制文件及其 rpcd 插件,或策略、rpcd 配置、/etc/openwrt-mcp 和请求状态执行 exec;以及对这两个配置执行 uci_apply、历史记录 restore 和 pkg_config_resolve。已经拥有 root shell 的客户端不在此底线的保障范围内:请参阅 SECURITY.md。设计说明:docs/luci-approvals.md。
在真实的 OpenWrt 25.12.5 路由器(包括 LuCI 手机布局)和 24.10.8 虚拟机上运行测试:LuCI 页面在无头 Chromium 中的渲染、批准、拒绝和 root shell 确认流程、rpcd 拒绝只读登录的写入调用,以及 Overview 小组件仅在有待处理请求时显示。稍后会添加截图。
为什么不用 .apk: 25.12 软件包是 apk-tools v3 ADB 归档。构建软件包需要 OpenWrt SDK,或带有 mkpkg 的主机 apk-tools(路由器上的 apk 没有 mkpkg),而未签名的软件包无论如何都需要使用 --allow-untrusted。安装为文件的静态二进制不会损失任何功能:/etc/config 会在 sysupgrade 中自行保留,而 /lib/upgrade/keep.d/openwrt-mcp 会保留二进制文件、init 脚本、rc.d 链接、状态和 LuCI 页面。(owut/ASU 镜像会重新构建软件包,而不是散装文件——keep.d 条目正是用来确保这些文件能在一次升级中保留的。)
连接
openwrt-mcp connect(推荐)
此版本也提供适用于 PC 的二进制文件(openwrt-mcp_<version>_windows_amd64.zip、darwin_arm64.tar.gz
等;Linux PC 可使用 linux_ 压缩包)。将其解压到 PATH 中的任意位置;它需要 OpenSSH,Windows 10 及更高版本、macOS 和 Linux 均已提供 OpenSSH。然后,在 PC 上运行:
openwrt-mcp connect --client claude-code --host 192.168.1.1 # or claude-desktop, codex, cursor, gemini, vscode
它会创建专用的 ed25519 密钥(~/.ssh/openwrt_mcp,绝不会覆盖现有密钥),打印需要在路由器上运行一次的两条命令,并显示要添加到客户端的配置项。添加 --write 后,它会完成最后一步:对于 Claude Code 和 Codex,调用它们各自的 mcp add;对于 Claude Desktop、Cursor、Gemini CLI 和 VS Code,则会将一个 openwrt 配置项合并到它们的 JSON 文件中(保留其他服务;原文件只备份一次,文件名为 <file>.before-openwrt-mcp;如果文件带有注释,则不修改文件,而是打印配置片段)。配置项会将 ssh 及其参数存为列表,因此密钥路径中含有空格时无需加引号,并且会设置保活选项,以便检测路由器重启。
openwrt-mcp connect doctor --host 192.168.1.1
会按顺序检查整个链路,遇到第一个故障时停止,并提供相应的修复方法:
[ ok ] ssh reachable 192.168.1.1:22
[ ok ] host key
[ ok ] key accepted /home/you/.ssh/openwrt_mcp
[FAIL] forced command the key logged in, but nothing answered as an MCP server (...)
fix: the key is probably authorized without its forced command ...
[skip] daemon socket
检查步骤依次为:SSH 可访问、主机密钥、密钥已接受、强制命令、守护进程套接字、initialize、tools/list。密钥错误、缺少强制命令、守护进程已停止以及套接字已禁用,都会分别指出对应的步骤。
重启或守护进程重启后。 重启、install.sh 升级或崩溃都会结束所有打开的会话:openwrt-mcp stdio 桥接进程会退出,并显示 daemon closed the connection,客户端会显示服务已断开连接。MCP 客户端并非都会自动重新连接,因此请从客户端重新连接(使用其 MCP 菜单,或重启客户端)。启动后,SSH 会比守护进程开始监听早几秒响应;桥接进程会等待最多 10 秒,等待守护进程套接字就绪,之后才会报错 daemon not reachable ... after waiting 10s(以及 [code: TIMEOUT; ...])。因此,路由器恢复运行后立即重新连接通常就能正常工作;如果不行,请等待几秒后再试。仍在等待 uci_confirm 的更改会在下次启动时回滚。openwrt-mcp status 会显示最近一次断开连接的情况及原因(client closed the connection、connection error: ... 或 the daemon restarted (router reboot, upgrade or crash));这些信息来自审计日志,日志中还会为每个会话记录一行 stdio session closed after ...,并在每次启动时记录一行 daemon started。
更精简的会话,以及 Shell
请求的权限少于密钥允许的权限。 策略决定客户端可以做什么;客户端也可以为某个会话请求更少的权限。
openwrt-mcp connect --client claude-code --host 192.168.1.1 --read-only # nothing that changes the router
openwrt-mcp connect --client claude-code --host 192.168.1.1 --toolset diag,pkg # only those tools
工具集包括 diag(logread、network_clients、firewall_show、net_diag、service_list、ubus_list、ubus_call)、
config(uci_get、uci_apply、uci_confirm、uci_rollback、service_control)、pkg(pkg_query、pkg_change、
pkg_config_diff、pkg_config_resolve、sysupgrade)和 wg(三个 WireGuard 工具)。system_status 始终可用;
exec 不属于任何工具集。--read-only 是与客户端授权取交集后的 @readonly 预设,因此它只能移除权限;调用隐藏工具时会被拒绝,而不仅仅是不显示在列表中。配置文件会作为强制命令后的参数传递(SSH_ORIGINAL_COMMAND),由守护进程按照固定语法解析:任何其他内容都会被拒绝并记入审计日志,而且其中没有任何参数可以扩大会话权限。
从 Shell 或脚本调用,无需 MCP 客户端。 同一个 SSH 密钥每次运行一个工具:
ssh -T [email protected] call --list
ssh -T [email protected] call uci_get --help
ssh -T [email protected] call uci_get '{"config":"network"}'
ssh -T [email protected] -- --read-only call uci_get '{"config":"network"}' # after a profile word, `--` ends ssh's own options
结果会输出到 stdout;拒绝或错误会连同 allow 行输出到 stderr,并以状态码 1 退出。调用会经过与 MCP 会话相同的策略、审计和脱敏流程。对于偏好使用 Shell 而非 MCP 的 Agent,skills/openwrt-mcp/SKILL.md 是一个可直接使用的技能文件。
限制。 每个工具都有一个高于其自身等待时间的截止时间,同时最多运行 8 个调用:其余调用最多等待 30 秒,之后会收到 busy 拒绝(TIMEOUT)。客户端取消调用时,会终止该调用启动的命令。uci_confirm、uci_rollback 和 mfa_unlock 无需等待空闲名额。
手动通过 SSH 使用 stdio
在路由器上绑定到 MCP 桥接程序且不授予其他权限的专用密钥:
ssh-keygen -t ed25519 -N '' -f ~/.ssh/openwrt_mcp
ssh root@router "openwrt-mcp authorize-key claude-code '$(cat ~/.ssh/openwrt_mcp.pub)'"
ssh root@router "openwrt-mcp allow claude-code @readonly 30d"
claude mcp add openwrt -- ssh -T -i ~/.ssh/openwrt_mcp -o BatchMode=yes root@router
authorize-key 会在 /etc/dropbear/authorized_keys 中写入类似
command="/usr/bin/openwrt-mcp stdio --client claude-code",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 ...
的一行:该密钥会以客户端 claude-code 的身份使用 MCP 服务,且无法打开 Shell 或转发端口。如果密钥已用于 root 登录,则会拒绝处理。
桥接程序会将会话转发到仅限 root 访问的 Unix 套接字(/var/run/openwrt-mcp/mcp.sock,位于权限为 0700 的目录中,套接字权限为 0600)。工具由守护进程运行,因此在一个会话中启动的回滚操作会在该会话结束后继续生效。套接字上的客户端名称是经过断言的,而非经过验证的——这是安全的,因为只有 root 才能连接,而 root 本就可以修改策略文件;SSH 密钥才是真正的身份验证方式。
在 Windows 上,内置的 OpenSSH 客户端(ssh.exe)可用于此操作;密钥请使用完整路径。
通过隧道使用 HTTP
与上游保持一致:openwrt-mcp pair <client> 会输出一次承载令牌;
然后执行 ssh -N -L 8730:127.0.0.1:8730 root@router;
接着执行 claude mcp add --transport http openwrt http://127.0.0.1:8730/mcp --header "Authorization: Bearer $TOK"。
守护进程拒绝绑定除回环地址以外的任何地址。
让策略真正发挥作用
如果 Agent 还能通过 Shell 工具运行 ssh root@router ...,那么策略就只是建议。请在客户端中禁止此操作(Claude Code:添加针对
Bash(ssh * root@<router>*) 的 permissions.deny 规则),或者将 root 密钥保存在 Agent 无法读取的位置。
策略
默认拒绝。策略会向一个客户端授予一组工具、范围 glob(* 和 ? 是通配符;方括号按字面匹配)、每分钟调用上限和有效期。对 /etc/config/openwrt-mcp 的更改无需重启即可生效。
openwrt-mcp allow claude-code @readonly 30d # every read tool; ubus_call on an allow-list of read methods
openwrt-mcp allow claude-code @operator 7d # + uci_apply/confirm/rollback, service_control, pkg_change,
# pkg_config_resolve, wg_new/remove_client, sysupgrade backup
openwrt-mcp allow claude-code uci_apply 'dhcp.* wireless.*.disabled' 30d
openwrt-mcp allow claude-code service_control 'dnsmasq.restart adguardhome.*' 30d
openwrt-mcp revoke claude-code # remove every policy for the client
openwrt-mcp prune --older-than 7d # delete grants that expired over a week ago (audited)
openwrt-mcp policies | status [--all] | clients
再次授予相同的工具和范围时,会替换客户端已过期的授权,而不是新增一条授权。status 会用一行统计过期授权;--all 会列出它们。
两个预设都不包含 exec 或不受限制的 ubus_call;这两者都会绕过安全防护。只读 ubus 列表明确列出 object.method 对,而非 *.list 这类 glob,因为 session.list 会返回 LuCI 会话 ID。uci_get '*' 会读取所有配置,但机密选项(Wi-Fi 密钥、WireGuard 密钥、密码、令牌)的值会以 <redacted> 返回;参见 安全模型。exec 不会隐藏敏感内容。
拒绝信息会指出未覆盖的范围,并打印能够覆盖它的确切 allow 行。
exec 范围按字面取 argv[0],因此授予 find 或 awk 看似无害,实则相当于授予 root shell:这两个命令都会运行其他程序,shell、env、nice、flock、timeout、xargs、tar、ssh、lua、ucode、apk 和 opkg 也一样。allow 会拒绝此类授权,除非你添加 --shell-equivalent;判断依据是基本名称,因此 /usr/bin/../bin/sh 和 /bin/* 也算在内;能够访问 file.exec 的 ubus_call 授权也会受到同样处理。openwrt-mcp policies 和 status 会标记此类授权,也包括手动编写的策略。此列表只是最低限度,并非安全保证:能够写入文件的授权(wget、cp、dd),或启动未列入其中的解释器,仍可能被滥用。
第二因素
与上游保持一致:openwrt-mcp mfa enrol <client> 会打印一个 otpauth:// URI;然后在策略中设置 list mfa_tools 'uci_apply' 和 option mfa_window '15m'。一个验证码可开启限时窗口;验证码只能使用一次;解锁状态仅保存在内存中。
仅注册不会启用任何限制。只有 /etc/config/openwrt-mcp 中带有 mfa_tools 的 config policy 段会启用限制,而且 mfa_tools 只能指定同一策略授予的工具。通过 openwrt-mcp allow 或 LuCI 页面授予的权限从不需要验证码。openwrt-mcp mfa status 会提示已注册客户端何时没有启用限制的策略。
错误验证码会受到限制:在 config server 段中,连续输错 option mfa_max_failures '5' 次后,该客户端将无法使用 mfa_unlock,锁定时间为 option mfa_lockout '300' 秒('5m' 也有效);每次再次触发时,锁定时间都会翻倍,最长为一小时。锁定期间输入正确验证码也会被拒绝,且不会消耗验证码。没有密钥的客户端也按相同方式计数,因此该限制不会泄露客户端是否已注册;计数器保存在内存中(重启、执行 mfa enrol 或轮换密钥都会清除计数器)。每次输错验证码都会记录为 ERROR,锁定则记录为 DENIED;验证码本身绝不会被记录。代价是:任何持有客户端令牌的人都可以让该客户端最长被锁定一小时。
安全模型
- 回滚机制能应对最可能发生的情况。
uci_apply会将修改过的配置快照保存到/etc/openwrt-mcp/rollback/<token>/(并执行 fsync),在提交前写入待处理记录,随后提交、重新加载并启动计时器(默认 90 s,最长 600 s)。如果没有调用uci_confirm,文件会被原子恢复,暂存的更改会被撤销,服务会重新加载。如果路由器在时间窗口内断电重启,procd 会在网络启动后(S95)启动守护进程,由其在启动时执行回滚,并为每个恢复的配置强制触发一次config.change。 - 不碰其他人的编辑。 只要要提交的配置存在其他会话尚未提交的更改,或有尚未确认的应用操作待处理,所有会提交配置的工具都会拒绝运行。
- 重新加载前先检查。 只要服务提供了检查器,试运行就会用它检查暂存配置(防火墙使用
fw4 check),并且只列出此次更改新增的问题。除非设置force=true,否则实际应用会拒绝这些问题;原本就有问题的配置仍可修复。dnsmasq、dropbear 等没有能检查暂存更改的检查器:它们会标记为“未检查”,而非“通过”。 - 写入后再读回。
uci_apply会在提交后、重新加载前重新读取每个配置;wg_new_client、wg_remove_client和pkg_change会检查之后的对等端表、配置和/etc/apk/world;service_control会将最终状态与操作进行对比。路由器接受了写入却未保留时,会返回NOT_APPLIED,而uci_apply的回滚仍处于待命状态。无法执行读回检查时,会显示“未验证”。位置段(@rule[3])无法进行对比,系统会统计其数量。 - 不静默覆盖。
uci_get会在末尾返回配置的修订版本。将其作为expected_revisions传回,如果期间配置发生更改(例如通过 LuCI 或其他客户端),应用操作会以CONFLICT拒绝。 - 管理路径受保护。 除非调用指定了
probe(ping 网关、解析域名)或设置了force=true,否则会拒绝更改 LAN 接口或其网桥、SSH 监听器,或允许 SSH 进入的防火墙区域和规则。重新加载后会运行探测,并报告结果。此类更改的回滚窗口默认为 180 s。规则集是静态的(LAN 加上 dropbear 指定的任何内容),因此适用于常见布局,但并非所有网络拓扑。 - 历史记录与恢复。 确认更改后,系统会保留更改前的配置版本(每个配置保留最新的
history_keep个版本,默认为 5,存放于/etc/openwrt-mcp/history/)。uci_get history=list会显示历史版本,history=diff:<id>会将某个版本与当前版本比较,uci_apply restore=<id>会通过相同的快照、检查和回滚计时器恢复某个版本。历史记录和快照一样包含密钥:权限为0600,显示时会遮蔽,并包含在sysupgrade备份中。设置option history_keep '0'可关闭此功能。 - 禁止自行提升权限。 无论授予何种权限,
uci_apply和pkg_config_resolve都会拒绝修改 openwrt-mcp 的策略配置,因此客户端无法为自己写入更宽泛的策略。策略只能通过路由器上的openwrt-mcp allow/revoke修改。 @operator权限很强。 对*使用uci_apply可访问所有 UCI 配置,包括dropbear、rpcd和uhttpd;pkg_change则会以 root 身份安装软件包。请仅为工作会话授予权限(2h),并将长期授权限制在较窄的范围内,例如'dhcp.* wireless.*.disabled'。- 名称会根据 UCI 语法进行验证,因此像
lan.ipaddr=x这样的段名称无法让 uci 操作策略批准范围之外的其他键。 - 任何地方都不使用 shell。 所有命令都以 argv 形式执行;可能被解释为选项的目标和名称(例如
-f)都会被拒绝。 - 生命线保护。
service_control不会停止或禁用 dropbear、network、rpcd 或 openwrt-mcp。sysupgrade不提供刷写操作——可用test验证镜像,再手动刷写。除非强制执行,否则wg_remove_client不会断开最近 3 分钟内完成过握手的隧道,也不会断开当前会话所用的隧道。 - 凭据不会进入对话。
wg_new_client会将新客户端的配置(包含私钥)写入套接字旁仅 root 可读的 RAM 文件,并返回公钥和要运行的命令。在路由器上运行openwrt-mcp wg-show <name>,即可在终端打印配置和二维码并删除该文件;未读取的文件会在 24 小时后或下次重启时删除。只有设置reveal=true才会将密钥放入结果,进而进入模型上下文和提供商日志。写入sysupgrade备份前会先创建权限为 0600 的备份,并且只保留最新的一份。审计日志会记录参数和摘要,不会记录工具输出,并会遮蔽看起来像密钥的字段。服务器私钥会从wg show dump读取后立即丢弃。 - 工具输出中的密钥会被遮蔽。
uci_get、uci_apply(差异和错误)、pkg_config_diff、pkg_config_resolve、uci_confirm、uci_rollback、system_status和ubus_call会保留行的结构,并将密钥选项(key、key1..key4、psk、password、sae_password、passphrase、private_key、preshared_key、token、pwd、任何包含secret的内容等)的值替换为<redacted>。审计日志使用相同的规则。exec、logread和诊断信息不会遮蔽,wg_new_client则是有意豁免的。在config server区段中:list redact_extra 'vendor_blob'可添加名称;option redact_output '0'可关闭遮蔽,并会记录在审计日志中。经过遮蔽的差异不会显示密钥的具体变化。 - 路由器输出不可信。 主机名、SSID、日志行和 DNS 应答都由其他设备决定。所有结果都会移除终端转义序列、控制字符、双向文本和零宽字符以及无效 UTF-8,并将行截断至 1024 字节(
exec、ubus_call、wg_new_client和配置工具除外)。network_clients、logread和net_diag的开头会显示一行[untrusted text: ...]。这能降低模型服从此类文本的可能性,但无法彻底消除风险,因此请勿在读取这些内容的会话中开放写入权限。 - 状态不会暴露到 Web。 如果状态目录、审计日志或套接字位于
/www、cgi-bin目录或 uhttpd 的home目录之下(包括通过符号链接指向这些位置),系统会拒绝使用。 - SECURITY.md 中提供了威胁模型。
- 上游的发现仍然适用: rpcd ACL 不会约束本地 ubus 套接字上的 root 进程,因此不会依赖它;rpcd 自带的应用/回滚功能会将状态保存在 rpcd 内存和 tmpfs 中,所以快照由我们自行管理并保存在闪存上。
已验证
在 GL-MT6000 上,使用 install.sh 中的步骤安装原版 OpenWrt 25.12.5,并完全按照 Claude Code 的方式从
Windows 驱动(使用带强制命令密钥的 ssh.exe 和 stdio 桥接):
- **安装:**通过 procd 服务运行并启用 respawn,设置
S95/K10链接,在0700目录中创建权限为0600的套接字,仅在127.0.0.1上启用 HTTP,在 dropbear 的authorized_keys中添加强制命令行;@readonly和限时@operator策略无需重启即可生效。 - **读取实时数据:**列出全部 23 个工具及其架构和服务端说明;
system_status、network_clients、wg_list_clients(重复名称警告)、service_list、pkg_query upgradable、pkg_config_diff(11 个真实.apk-new文件、设置级别差异)、firewall_show check、从br-WAN执行的net_diag ping、logread过滤、uci_get、sysupgrade list、ubus_call。 - 可回滚的写入(在临时 UCI 配置上):
uci_apply(包括set_list)-> 在截止时间自动回滚,文件恢复为逐字节一致,并移除pending.json;有待处理操作时拒绝第二次 apply;uci_confirm;按需执行uci_rollback;dry_run。 - **WireGuard:**使用预共享密钥执行
wg_new_client(下一个可用地址,将对等端提交到 UCI 并热添加到内核,CGNAT 端点警告),列出后再执行wg_remove_client——/etc/config/network恢复为逐字节一致,内核中的对等端已移除。1.3.0 的交接流程(第二次运行,在通过uci_apply为测试启用、之后又禁用的接口上运行,配置修订版本与之前完全一致):结果不包含私钥;配置位于/var/run下的0700目录中,是仅 root 可读写的0600文件;在操作员终端运行openwrt-mcp wg-show会打印配置和 QR 码,并删除该文件。1.4.0 的回读会针对uci_apply、wg_new_client和wg_remove_client打印Verified: ...,每项操作都在启用回滚后应用并确认。 - **崩溃恢复:**守护进程在未确认 apply 时被
SIGKILL;procd 在 6 s 后重新启动它,并在启动时回滚更改。 - 拒绝情况:
exec、ubus_call session.list、超出范围的uci_apply(每项都会指出授权行)、对策略配置执行的任何uci_apply、未经身份验证的 HTTP(已审计)。 - 输出安全(1.1.0),在同一台设备上:
uci_get wireless和ubus_call network.wireless status对每个 Wi-Fikey都返回<redacted>,而ssid、encryption和类似名称(如wpa_disable_eapol_key_retries)不受影响(JSON 回复中的数字、布尔值和数组也一样)。设置 key 的uci_applydry run 会对其进行脱敏,且不会留下暂存内容。network_clients和logread的输出以不可信文本标记开头。没有密钥的客户端输入 5 个错误的mfa_unlock代码后会被锁定 5 分钟(第 6 次会被拒绝并提示重试时间);审计日志会记录 4 条ERROR,随后是DENIED,且不会出现任何提交的代码。通过ubus call log write写入的 syslog 行包含 CSI 和 OSC 序列、颜色代码、BEL 及双向文本覆盖字符(这些内容在路由器自己的日志中都存在),经logread返回时只剩文本和制表符;超长行则会截断到 1024 字节,并附上[+N bytes]说明。路由器上捕获了uci changes的语法(+=、-=、'\''、用于删除的裸-path),并将其作为测试固定数据。 - 可靠更改(1.2.0),在同一台设备上通过守护进程的 stdio 桥接驱动:
fw4 check会识别暂存在 uci 中的更改(对于无效值,它会以 0 退出并打印[!]行,验证逻辑会读取这些行):设置错误区域值的 dry run 会将其报告为新的问题,实际 apply 会被拒绝,且不会留下暂存或快照内容。过时的expected_revisions会导致CONFLICT;当前值可通过检查;调用未更改的配置对应的值会被拒绝。对 LAN 接口执行 dry run 会指出管理路径;未进行探测就实际 apply 会被拒绝,且不会留下暂存内容。若system.ntp.server中已存在某个元素,则会跳过对该元素执行add_list。在luci的临时选项上:执行带有ping和resolve探测的 apply 后,重载完成时两项都会报告 OK,并启用回滚;确认后会留下历史记录(0700目录,0600文件);history=diff会显示更改添加的内容;restore会将文件恢复为逐字节一致并保留原权限,执行探测并留下自己的记录;探测失败的 apply 会报告PROBE FAILED,同时仍启用回滚,而uci_rollback会将文件恢复为逐字节一致。守护进程的service_control restart会报告其状态“Settled after”。 - **接入与凭据(1.3.0),在同一台设备上:**使用
install.sh安装构建版本,openwrt-mcp version报告 1.3.0。Windows PC 上使用真实 OpenSSH 执行connect doctor,七个步骤全部通过并列出 23 个工具;使用路由器不认识的密钥时会停在 key accepted,端口关闭时则停在 ssh reachable,两种情况都会给出修复方法。为 Cursor 执行connect --write时,使用 PC 自带的ssh-keygen生成密钥,将条目合并到 JSON 文件中;再次运行时会报告条目未更改。diag会打印版本、设备板型、策略结构和审计行,将 SSH 客户端地址显示为ip-1,且不包含审计参数;--detail会额外显示脱敏后的摘要。sysupgrade backup创建了权限为-rw-------的归档(4.3 MB,因此sysupgrade -b会保留已存在文件的权限);第二次备份会删除第一次备份。重启后守护进程的常驻内存约为 10 MB。 - **会话清理(1.3.0),在同一台设备上:**守护进程重启后,在新构建版本上启动的 stdio 桥接会立即退出;客户端的下一次调用会打开新会话,且不会报错。仍运行旧构建版本的桥接会继续运行,直到客户端发出下一个请求。
status会将 45 个过期授权合并为一行;prune会将其移除,写入一条prune审计记录,守护进程重新加载后仅保留有效授权。 - 诊断(1.4.0),在同一台设备上,由
@readonly客户端执行:system_status mode=doctor列出一项低严重性发现:有三个等待合并的.apk-new文件;这与实际情况一致,且没有误报。mode=audit列出允许从 WAN 接入 WireGuard 端口的防火墙规则(确实存在且符合预期)和监听所有地址的uhttpd(确实如此),并为每项提供 wiki 链接和下一步调用。logread mode=summary将一千多行日志合并成约 50 条不同消息。先保存基线,再在定时任务运行后进行比较,每次都会将一条监控日志计为新增,因为其中的 uptime 字段发生了变化;因此将时长替换为<dur>,修复后同样的往返比较不再报告新增内容。net_diag wifi_survey会给出各无线电当前信道的 busy、rx、tx 和 noise 数值,与当天稍早读取的原始iwinfo survey数据相差不超过 1 个点;traffic会采样接口 3 秒,并按字节数对 conntrack 来源排序;usage会列出 nlbwmon 中当前月度周期的设备;network_clients会显示AIR列,其占比在每个无线电上相加约为 100%。 - **连接范围与韧性(1.5.0),在同一台运行 25.12.5 的设备上:**按安装程序的步骤部署构建版本。
service_list detail=读取 tailscale、AdGuard Home 和 podkop。dry-run 警告和 WireGuard 写入操作都在为测试创建的临时接口上运行(绝不使用 LAN 或真实隧道),之后删除该接口:通过启用回滚的路径添加并移除对等端,以及将对等端恢复回来的回滚操作。通过 SSH 从 PC 执行openwrt-mcp call,以及使用--toolset diag --read-only的会话。硬件测试发现,移除一个并非最后一个的对等端后会错误地报告NOT_APPLIED(UCI 匿名节 ID 按位置排列,因此回读时检查了错误的节);此问题已修复,测试用的假实现现在会像 UCI 一样重新编号。 单元测试和端到端测试(通过内存传输和桥接握手运行真实 MCP 客户端)可在任何操作系统上针对模拟路由器运行:apply/confirm/rollback/timeout、在时间窗口内重启、dry run、拒绝外部暂存的编辑、列出操作、针对捕获的 25.12 配置执行 WireGuard 添加/列出/移除、客户端加入、日志筛选、apk 模拟、.apk-new回滚解析、预设内容、范围 glob 语义。
尚未在硬件上验证(仅由模拟路由器测试覆盖):带 commit 的 pkg_change、LuCI 页面渲染、真实 /www 路径或符号链接上的 Web 根目录保护、真实断电重启(恢复路径与 SIGKILL 测试所测试的相同),以及真实 sysupgrade 期间的 keep.d。对于 1.2.0,还包括:应用管理路径变更时带有探测(仅运行了不带探测时的拒绝测试)、探测失败后等待回滚计时器触发而非执行 uci_rollback,以及 sysupgrade 后的历史记录。对于 1.3.0,还包括:使用真实 Claude Code、Codex、Claude Desktop、Gemini 或 VS Code 执行 connect --write(仅测试了文件合并和命令行)、macOS 上的任何操作,以及路由器上的安装程序下载路径(发布工作流已运行并发布了 1.2.0 和 1.3.0,但尚未对这些版本运行 install-router.sh)。对于 1.4.0,还包括:硬件上仅对 uci_apply、wg_new_client 和 wg_remove_client 执行了写入后的回读;service_control 和 pkg_change 是针对模拟路由器运行的,日志摘要也只查看过一台路由器的日志。WireGuard 移交的文件模式已在 CI 中通过 Linux 断言。欢迎提交其他板卡的报告。
致谢
-
GlassOnTin/openwrt-mcp(Ian Williams,MIT)——本项目的基础。沿用了其中的内容:运行在路由器上的静态 Go 守护进程(由 procd 管理)、默认拒绝的策略(带范围 glob)、速率限制和过期机制、以摘要形式存储的承载令牌、TOTP 时间窗口、带机密信息脱敏的 JSONL 审计日志、仅限 loopback 的 HTTP、ubus 回复裁剪,以及
uci_apply的确认或回滚机制。上游项目的 git 历史已保留在本仓库中。 -
jsebgiraldo/openwrt_ssh_mcp——一个路由器外运行的 Python 服务(每次调用都通过 SSH,使用正则表达式进行命令白名单过滤),已作为思路参考。以下四项经过改造后在此重新实现:
- 刷写前验证固件映像(
sysupgrade -T->sysupgrade的test操作); - 固件和板卡信息(
system_status的一部分); - 软件包管理(针对
apk重写:pkg_query、pkg_change); - ping / traceroute / nslookup(
net_diag)。
未采用的内容:OpenThread 边界路由器工具(依赖特定硬件)、
opkg和iptables命令(在 25.12 中已移除)、刷写固件工具(远程操作不可逆且无法恢复),以及对 shell 字符串进行正则表达式白名单过滤(改用 argv + 策略范围)。 - 刷写前验证固件映像(
贡献和安全
欢迎提交 Bug 报告和拉取请求——请参阅 CONTRIBUTING.md。请按照 SECURITY.md 中的说明私下报告漏洞,不要在公开议题中报告。计划中的工作请参阅 ROADMAP.md。
**报告 Bug:**在路由器上运行 openwrt-mcp diag,并将输出粘贴到议题中。它会打印版本、板卡、守护进程状态、策略概要和最近 20 条审计记录(--audit N);其中所有 IPv4、IPv6 和 MAC 地址、主机名、DHCP 名称、SSID、域名和 WireGuard 对等端名称都会替换为占位符(ip-1、mac-1、host-1、ssid-1),且同一值始终替换为相同占位符。它绝不会打印审计参数;只有使用 --detail 时才会打印条目的范围、摘要和错误文本,并以相同方式脱敏。名称会根据路由器自身对名称的描述进行匹配,因此粘贴前请先查看一次输出:仅出现在自由文本中、而未出现在路由器配置中的名称无法识别。
Licence
MIT -- see LICENSE. Copyright (c) 2026 Ian Williams (upstream) and Stanislav Chupin (OpenWrt 25.12 port).
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。