OpenWrt MCP について
OpenWrt MCPは標準のOpenWrtルーター上で動作し、Claude CodeなどのMCPクライアントから設定を確認・管理できます。デフォルト拒否の権限設定、監査ログ、秘密情報のマスキング、ロールバックを備えた設定変更を組み合わせています。
主な機能
- 01ルーターの状態、ログ、ネットワークを確認
- 02検査とロールバック付きでUCI設定を適用
- 03パッケージとサービスを管理し、ファイアウォールを表示
- 04WireGuardピアを管理
- 05クライアント別の権限設定と監査ログ
活用シーン
- 01ルーターの状態とセキュリティ上の所見を確認
- 02設定変更を準備し、確定またはロールバック
- 03ネットワーククライアントとファイアウォール規則を確認
- 04パッケージとWireGuardピアを管理
MCP 対応
ルーター上のサーバーは、SSH経由のstdioまたはloopback HTTPでMCPクライアントに23個のツールを公開します。ツールへのアクセスは権限で制御され、外部接続を行う操作はMCPアノテーションで示されます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
openwrt-mcp -- OpenWrt 24.10 および 25.12 向け MCP サーバー
MCP サーバーが OpenWrt ルーター上で動作するため、 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、 約 22 KB のツール説明に 23 個のツールを収録しています。すべてのリリースファイルにはチェックサムとビルド証明書が付属します。
GlassOnTin/openwrt-mcp をベースにしています。Ian Williams によるこのプロジェクトは、GL.iNet ファームウェア 4.x(OpenWrt 21.02、
opkg)を対象としています。本プロジェクトは stock 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>.apk-newではなく<file>-opkgとして配置され、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)。 - OpenWrt と Go の対応 CPU アーキテクチャ: リリースには各アーキテクチャ(arm64、armv5/v7、mips/mipsle softfloat、mips64/mips64le、x86、x86-64、riscv64)用のバイナリが含まれています。
install.shはルーターのDISTRIB_ARCHを読み取って該当するバイナリを選ぶか、ソースからビルドする場合はクロスコンパイルします。
OpenWrt 25.12.5 を実行する GL.iNet GL-MT6000(mediatek/filogic、aarch64_cortex-a53)でテスト済みです。
コードには、このボード固有のものはありません。
ほかの OpenWrt MCP サーバーとの違い
各プロジェクトの README に基づき、2026-10-07 時点で記載しています。「記載なし」は 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 ページ「サービス -> 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 |
| スコープのグロブ | [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 から取得)、リースを表示します。1 ページあたり 100 行で、次のページには offset を指定します。 |
firewall_show | ツール | nft list ruleset、fw4 print、fw4 check、単一のテーブルまたはチェーンを表示します。 |
net_diag | <action>[.<target>] | ping/traceroute/nslookup(任意の指定インターフェースから実行可能)、ルート、ポリシールール、近隣情報を確認します。wifi_survey は、無線ごとに使用中のチャネルの混雑状況(ビジー、rx と tx のエアタイム、ノイズ)を報告します。他のチャネルは測定されないため、おすすめすることはありません。traffic は数秒間インターフェースの速度をサンプリングし、送信元別に conntrack テーブルを順位付けします。usage は、会計期間(デフォルトは 1 か月)におけるデバイスごとの nlbwmon の合計値を読み取ります。これら 3 つはすべて読み取り専用です。 |
uci_get | <config>[.<section>[.<option>]]、名前で検索する場合は refs | uci show を表示し、必要に応じて安定した cfgXXXXXX ID を含めます。最後に設定のリビジョンを表示します。history=list/diff:<id> は、確定した各変更より前の保存バージョンを表示します。refs=NAME は、設定全体から名前を定義・使用している箇所を検索します。 |
uci_apply | 変更ごとに <config>.<section>[.<option>]、restore には <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 を確認します。1 ページあたり 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 で確定します)。force=true を指定しない限り、直近 3 分以内に接続したピア、または現在のセッション自身のピアは拒否されます。 |
ubus_list | (ゲート制限なし) | オブジェクト、メソッド、引数シグネチャをすべて検出します。 |
ubus_call | <object>.<method> | バス上のその他の呼び出しを実行します。8 KB を超える応答では、長い配列が省略されます。 |
exec | argv[0] | シェルを介さず、1 つのプログラムを実行します。sh、find、awk、env、ssh などへの許可は root シェルに相当するため、--shell-equivalent を指定しない限り allow は拒否します。 |
mfa_unlock | (ゲート制限なし) | MFA で保護されたツール用に TOTP の有効時間を開始します。 |
プロンプト。 MCP プロンプトを表示するクライアント(通常はスラッシュコマンドとして表示します)には、router-health、who-is-online、secure-my-router、wifi-doctor、upgrade-plan の 5 つのレシピが用意されています。それぞれ、呼び出すツールを指定する数行の内容で、変更ではなく、レポートまたは提案で締めくくられます。@readonly プリセットで許可されるものだけを使用するため、読み取り専用のクライアントでも最後まで実行でき、使用するまでは費用がかかりません。いずれかを一覧表示または取得してもルーターでは何も実行されないため、ポリシーは適用されず、監査記録にも残りません。 |
警告、参照、ピア、アドオン
ドライラン時の警告。 uci_apply dry_run の最後に表示される warnings from this change は、変更によって新たに発生する問題を示します。監査結果と同じ順序で並び、それぞれに次の手順と OpenWrt wiki のページが示されます。これはあくまで助言であり、変更を拒否するものではありません。対象となるのは、インターネットに公開されるポートまたはポート転送、ACCEPT に設定された WAN ゾーンの入力または転送、SSH パスワードログインの有効化、暗号化されていない SSID、他のセクションでまだ使われている名前を変更で削除または誤記すること(ref-removed、ref-unknown。大文字と小文字の不一致の場合は「もしかして」の候補も表示)、および現在のセッションが接続に使っている Wi-Fi ネットワークを無効にする変更(radio-in-use)です。変更前から存在する問題は再報告されません。実際の適用ではこれらの警告は繰り返されないため、先にドライランを実行してください。
管理経路はセッションに追従します。 LAN、そのブリッジ、SSH リスナー、SSH 用のゾーンとルールに加えて、SSH 接続が到達するインターフェースも管理経路として扱われます(デーモンは ip route get でクライアントアドレスを解決します)。そのため、接続に使っている WireGuard トンネルの変更には、LAN の変更と同様にプローブまたは force=true が必要です。
インターフェース、ゾーン、デバイスの名前を変更または削除する前に。 uci_get refs=lan は、その名前を定義しているもの(ネットワークインターフェース、ファイアウォールゾーン、デバイス、無線、mwan3 メンバーまたはポリシー)と、それを使っているすべての参照フィールドを一覧表示します。対象フィールドは、ゾーンの network、ルール/転送/リダイレクトの src と dest、dhcp の interface、wireless の network と device、network の device、ports、route.interface、およびインストールされている場合の sqm と mwan3 です。「defined as: nothing」は、その名前を持つセクションがない(誤記または大文字と小文字の不一致)ことを意味します。読み取られるのはこれらのフィールドだけなので、この検索でキーを探すことはできません。スコープ refs が必要です。これは @readonly と @operator に含まれます。config=firewall を指定すると、1 つの設定に絞り込めます。
WireGuard ピアの追加と削除は、ロールバックが有効な通常の変更です。 ピアは /etc/config/network に書き込まれ、netifd が読み込むため、他のピアのセッションは維持されます。どちらのツールも、実行中のインターフェースでピアが表示される(または表示されなくなる)まで待機し、確認した内容を伝えます。uci_confirm を実行するまでは変更は永続化されません。実行しない場合、タイムアウト後または再起動後にピアは再び削除され、新しいクライアントのキー ファイルも削除されます。一度に確認待ちにできる変更は 1 つだけです。自分のセッションが到達する経路のピアを削除するには、force=true が必要です。
アドオンのステータス。 service_list detail=NAME は、インストール済みの(init スクリプトが存在する)アドオンを読み取り、読み取り専用で固定のフィールドセットを出力します。設定ファイルやコマンドの生出力は表示しません。これらにはログインハッシュ、プロキシリンク、ノードキー、トークンが含まれるためです。権限付与にはサービスのスコープ(tailscale.detail、または @readonly に含まれる *)が必要です。通常の一覧表示には必要ありません。テールネットのピアのホスト名など、第三者が設定したテキストは切り詰められ、サニタイズされたうえで、信頼できないデータとして示されます。
| アドオン | 報告内容 | 検証済み |
|---|---|---|
| tailscale | 状態、テールネットの DNS サフィックス、このノードのアドレス、広告されたルート、出口ノード、健全性、ピア(オンライン状態、直接接続または中継、トラフィック) | GL-MT6000、25.12 |
| AdGuard Home | 稼働状態、リッスンアドレス、保護とフィルタリングのスイッチ、上流サーバー(スキームとホストのみ)、ブートストラップ、フィルターリストとルールの件数、dnsmasq が転送先として指定されているかどうか | GL-MT6000、25.12 |
| podkop | バージョン、sing-box の稼働状態、nft テーブル、dnsmasq のパス、設定、セクションごとに 1 行(タイプ、インターフェース、コミュニティリスト、動的リストのサイズ) | GL-MT6000、25.12 |
意図的に読み取らないもの:AdGuard Home のウェブ 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を指定しない限りデーモンの再起動を拒否します。 - 事前にバイナリーがオーバーレイに収まることを確認します。
./install.sh uninstall [--purge] でアンインストールできます。
LuCI から承認する
インストール後、LuCI の Services -> MCP Server には、デーモン(実行中または停止中、バージョン、HTTP リスナー、stdio ソケット)、自動ロールバック期限付きの保留中 uci_apply、ペアリング済み HTTP クライアント、常設ポリシー、監査ログが表示されます。ここでエージェントへの応答も行えるため、allow の行を貼り付けるためにシェルを開く必要はありません。
- エージェントが許可されていないツールを呼び出します。呼び出しは拒否され、リクエストが記録されます。クライアント、ツール、失敗した正確なスコープ、秘匿化された引数はすべてデーモンが導出し、エージェントからのテキストは含まれません。
- リクエストは MCP ページの上部と Status -> Overview のウィジェットに表示されます。このウィジェットは保留中の項目がある場合にのみ表示されます。
- Allow は許可を追加し、Deny は拒否します。デフォルトの許可範囲は最も限定的です(このツールをこの正確なスコープで、1 時間)。選択項目を使うと、範囲を広げ(スコープのファミリー、すべてのスコープ、
@operatorプリセット)、期間を 15 分から 7 日の間で設定できます。root シェルツールのリクエストには警告が表示され、2 回目の確認が必要です。 option request_wait '300'(秒単位、0~600、デフォルトは 300)を設定すると、拒否された呼び出しは保留され、許可が適用されると自動的に続行されるため、エージェントが再試行する必要はありません。0 の場合はただちに拒否されます。この設定がない場合、または待機時間が切れた場合は、エージェントにリクエスト 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 シェルを許可します。上限は 24 時間(「無期限」は選択できません)で、チェックボックスのオンとクライアント名の入力が必要です。また、二要素認証を登録済みのクライアントには拒否されます。有効な間は両方のページに赤いバナーが表示され、End now を選択できます。この設定によって許可された呼び出しは、監査ログに "via":"permit-all" と記録されます。CLI での同等の操作は openwrt-mcp allow <client> @all 1h --shell-equivalent です。リクエストを 1 件ずつ承認する方法を推奨します。
任意のチャネルはインストール後はオフになっています。それぞれ Settings に切り替えスイッチがあり、悪影響についての注意書きが横に表示されます。各スイッチの操作は監査ログに記録されます。
| チャネル | オプション | 動作 | 想定されるリスク |
|---|---|---|---|
| LuCI からペアリング | pairing | Clients タブで、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 シェルツールや すべて許可 には適用されません。 | クライアントが回答を中継します。プロンプトに自動回答するクライアントやエージェントは、自分自身に権限を与えてしまう可能性があります。 |
エージェントにできないこと。 LuCI 管理者はすでに root であるため、ボタンを設けても人に新たな権限を与えることにはなりません。防止しなければならないのは、エージェント自身が権限を拡大することです。そのため、組み込みの拒否対象によって、どの権限が付与されていても(@operator と @all を含む)、次の操作を拒否します。オブジェクト openwrt-mcp、session、file、rpc-sys に対する ubus_call と、openwrt-mcp および rpcd の設定に対する uci の書き込み。バイナリとその rpcd プラグイン、ポリシー、rpcd の設定、/etc/openwrt-mcp、リクエスト状態に対する exec。また、これら 2 つの設定に対する uci_apply、履歴の restore、pkg_config_resolve です。すでに root シェルを持つクライアントに対しては、この拒否対象で保証できる範囲を超えています。SECURITY.md を参照してください。設計メモ:docs/luci-approvals.md。
実機の OpenWrt 25.12.5 ルーター(LuCI のスマートフォン向けレイアウトを含む)と 24.10.8 VM で実行しました。LuCI ページのヘッドレス Chromium でのレンダリング、承認・拒否・root シェルの確認フロー、読み取り専用ログインでの書き込み呼び出しに対する 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 イメージで再構築されるのはパッケージであり、個別のファイルではありません。このエントリーがあることで、sysupgrade を 1 回またいで保持されます。)
接続
openwrt-mcp connect(推奨)
リリースには、お使いの PC 用のバイナリも含まれています(openwrt-mcp_<version>_windows_amd64.zip、darwin_arm64.tar.gz など。Linux PC では linux_ アーカイブを使用できます)。任意の場所に展開し、PATH に追加してください。OpenSSH が必要ですが、Windows 10 以降、macOS、Linux には搭載されています。次に、PC で以下を実行します。
openwrt-mcp connect --client claude-code --host 192.168.1.1 # or claude-desktop, codex, cursor, gemini, vscode
専用の ed25519 キー(~/.ssh/openwrt_mcp。既存のキーは上書きしません)を作成し、ルーターで一度だけ実行する 2 つのコマンドを表示して、クライアントに追加するエントリーを示します。--write を追加すると、最後の手順も実行します。Claude Code と Codex では各ツール独自の mcp add を実行し、Claude Desktop、Cursor、Gemini CLI、VS Code では JSON ファイルに openwrt エントリーを 1 つマージします(ほかのサーバーはそのまま残ります。元のファイルは <file>.before-openwrt-mcp として 1 回だけ保存されます。コメントを含むファイルは変更せず、代わりにスニペットを表示します)。エントリーでは ssh とその引数をリストとして保存するため、キーのパスにスペースが含まれていても引用符で囲む必要はありません。また、キープアライブを設定し、再起動したルーターを検知します。
openwrt-mcp connect doctor --host 192.168.1.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 ... の行が 1 つ、起動ごとに daemon started の行が 1 つ記録されます。
より限定的なセッションとシェル
鍵で許可されている範囲より少ない権限を要求します。 ポリシーはクライアントが実行できる操作を定めますが、クライアントは各セッションで、それより少ない権限を要求することもできます。
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 の 3 つのツール)です。system_status は常に使用でき、
exec はどのツールセットにも含まれません。--read-only は、クライアントに許可された権限と @readonly プリセットの共通部分なので、
権限を削ることしかできません。また、非表示のツールを呼び出すと拒否されます。単に一覧から除外されるだけではありません。プロファイルは強制コマンドの後に続く語句(SSH_ORIGINAL_COMMAND)として渡され、デーモンは固定の文法に照らして解析します。それ以外はすべて拒否され、監査ログに記録されます。また、セッションの権限を広げる語句はありません。
シェルまたはスクリプトから、MCP クライアントを使わずに実行します。 同じ SSH 鍵で、1 回に 1 つのツールを実行できます。
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
結果は標準出力にテキストで出力されます。拒否やエラーは allow 行と終了ステータス 1 とともに標準エラー出力に出力されます。呼び出しは、MCP セッションと同じポリシー、監査、機密情報のマスキングを通じて実行されます。skills/openwrt-mcp/SKILL.md は、MCP よりシェルを好むエージェント向けの、そのまま使えるスキルファイルです。
制限事項。 各ツールには、ツール自体の待機時間に加えて期限が設定されており、同時に実行できる呼び出しは最大 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 は、command="/usr/bin/openwrt-mcp stdio --client claude-code",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 ... のような行を
/etc/dropbear/authorized_keys に書き込みます。この鍵では claude-code クライアントとして MCP サーバーが使われ、
シェルの起動やポート転送はできません。すでに 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" を実行します。
デーモンがバインドを許可するのはループバックのみです。
ポリシーを実効性のあるものにする
エージェントがシェルツール経由で ssh root@router ... も実行できる場合、ポリシーはあくまで助言にすぎません。クライアント側でそれを禁止するか(Claude Code の場合は、Bash(ssh * root@<router>*) に対する permissions.deny ルールを設定します)、エージェントが読み取れない場所に root 鍵を保管してください。
ポリシー
デフォルトでは拒否します。ポリシーでは、1 つのクライアントに対して、ツールのリスト、スコープの glob(* と ? はワイルドカード、角括弧は文字として扱います)、1 分あたりの呼び出し上限、および有効期限を付与します。
/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 は期限切れの付与を 1 行で数え、--all はそれらを一覧表示します。
どちらのプリセットにも exec と制限のない ubus_call は含まれません。どちらも安全対策を回避するためです。読み取り専用の ubus リストは、*.list のような glob ではなく、明示的な object.method の組み合わせです。これは session.list が LuCI のセッション ID を返すためです。uci_get '*' はすべての設定を読み取りますが、シークレットオプション(Wi-Fi キー、WireGuard キー、パスワード、トークン)の値は <redacted> として返されます。安全性モデル を参照してください。exec はマスクされません。
拒否された場合は、許可されていないスコープが示され、それを許可する正確な allow 行が出力されます。
exec のスコープは、文字どおりの argv[0] です。そのため、find や awk を許可すると、無害に見えてもルートシェルになります。どちらも別のプログラムを実行でき、シェル、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' を設定します。コードを 1 つ入力すると、時間制限付きのウィンドウが開きます。コードは 1 回のみ使用でき、ロック解除情報はメモリ内にのみ保存されます。
登録しただけでは何も制限されません。/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' を設定すると、連続して 5 回コードを間違えたクライアントは、option mfa_lockout '300' 秒('5m' も使用できます)の間 mfa_unlock をロックされます。この時間はロックアウトのたびに倍になり、最大 1 時間です。ロックアウト中に正しいコードを入力しても拒否され、使用済みにはなりません。シークレットを持たないクライアントも同じように数えられるため、この制限から登録の有無が漏れることはありません。カウンターはメモリ内に保存されます(再起動、mfa enrol、またはシークレットのローテーションでクリアされます)。間違ったコードはそれぞれ ERROR として監査ログに記録され、ロックアウトは DENIED として記録されます。コード自体がログに記録されることはありません。代償として、クライアントのトークンを持つ者は、そのクライアントを最大 1 時間ロックアウトできます。
安全性モデル
- 最も起こりやすい事態にも耐えるロールバック。
uci_applyは変更対象の設定を/etc/openwrt-mcp/rollback/<token>/にスナップショットとして保存(fsync 済み)し、コミットする前に保留レコードを書き込み、コミットして再読み込みした後、タイマー(デフォルト 90 秒、最大 600 秒)を設定します。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で拒否されます。 - 管理経路を保護します。 LAN インターフェースまたはそのブリッジ、SSH リスナー、あるいは SSH の接続を許可するファイアウォールのゾーンとルールを変更する場合、呼び出しで
probe(ゲートウェイへの ping、名前解決など)を指定するか、force=trueを指定しない限り拒否されます。プローブは再読み込み後に実行され、結果とともに報告されます。このような変更では、ロールバック時間はデフォルトで 180 秒です。ルールセットは静的(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は、dropbear、rpcd、uhttpdを含むすべての UCI 設定にアクセスでき、pkg_changeは root としてパッケージをインストールします。作業セッション用に付与し(2h)、長期間有効な権限は'dhcp.* wireless.*.disabled'のような限定的なスコープに留めてください。- 名前は UCI の文法に照らして検証されるため、
lan.ipaddr=xのようなセクション名によって、ポリシーで承認されたスコープとは別のキーを uci に操作させることはできません。 - シェルは一切使いません。 すべてのコマンドは 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>を実行すると、端末に設定と QR コードが表示され、ファイルが削除されます。回収されなかったファイルは 24 時間後、または次回の再起動時に削除されます。reveal=trueを指定した場合に限り、鍵が結果に含まれ、モデルのコンテキストとプロバイダーのログにも残ります。sysupgradeのバックアップは、書き込みを始める前に0600で作成され、最新の 1 件だけが保持されます。監査ログには引数と要約が記録され、ツールの出力は記録されません。また、秘密情報らしきフィールドはマスクされます。サーバーの秘密鍵は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: ...]という行で始まります。これによりモデルがそのようなテキストに従う可能性は下がりますが、完全になくなるわけではありません。そのため、そのテキストを読み取るセッションでは書き込みスコープを与えないでください。 - 状態情報はウェブに公開されません。 状態ディレクトリ、監査ログ、ソケットが
/www、cgi-binディレクトリ、または uhttpd のhomeの配下にある場合、シンボリックリンクを介している場合も含めて拒否されます。 - 脅威モデルについては SECURITY.md を参照してください。
- 上流の調査結果も引き続き当てはまります。 rpcd ACL はローカルの ubus ソケット上の root プロセスを制限しないため、それに依存していません。また、rpcd 自身の適用/ロールバック機能は状態を rpcd のメモリと tmpfs に保持します。そのため、スナップショットは独自に作成し、フラッシュ上に保存しています。
検証済み
install.sh の手順でインストールした GL-MT6000 の標準 OpenWrt 25.12.5 で、Claude Code とまったく同じ方法(強制コマンドキーを使った ssh.exe と stdio ブリッジ)で Windows から操作した場合:
- インストール: respawn を設定した procd サービス、
S95/K10リンク、0700ディレクトリ内のソケット0600、127.0.0.1のみで HTTP を待ち受け、dropbear のauthorized_keysに forced-command 行を設定、@readonlyと有効期限付きの@operatorポリシーは再起動なしで反映されます。 - 稼働中データの読み取り: スキーマとサーバーの指示を含む、一覧に記載された 23 個すべてのツール;
system_status、network_clients、wg_list_clients(名前重複の警告)、service_list、pkg_query upgradable、pkg_config_diff(実在する.apk-newファイル 11 個、設定レベルの差分)、firewall_show check、br-WANからのnet_diag ping、logreadのフィルタリング、uci_get、sysupgrade list、ubus_call。 - ロールバックを伴う書き込み(スクラッチ UCI 設定で実施):
set_listを含むuci_apply-> 期限到来時に自動ロールバックされ、ファイルはバイト単位で完全に元に戻り、pending.jsonは削除されます;保留中に 2 回目の適用を試みると拒否されます;uci_confirm;任意のタイミングでのuci_rollback;dry_run。 - WireGuard: 事前共有鍵を使った
wg_new_client(次に空いているアドレス、ピアは UCI にコミットされ、カーネルにホット追加されます。CGNAT エンドポイントの警告あり)、一覧表示後にwg_remove_client--/etc/config/networkはバイト単位で完全に元に戻り、カーネルのピアは削除されました。1.3.0 の受け渡し(2 回目の実行。テストのためuci_applyで有効化し、終了後に再び無効化したインターフェース上で実施し、設定リビジョンは実行前と同一):結果に秘密鍵は含まれません;設定は/var/run以下の0700ディレクトリ内にある、root のみ読み書き可能な0600ファイルでした;オペレーターの端末から実行したopenwrt-mcp wg-showは設定と QR コードを表示し、ファイルを削除しました。1.4.0 の読み戻しでは、ロールバックを有効にした状態でそれぞれ適用し、その後確認したuci_apply、wg_new_client、wg_remove_clientに対してVerified: ...と表示されました。 - クラッシュ復旧: 未確認の適用がある状態でデーモンに
SIGKILLを送ると、6 秒後に procd が再起動し、起動時に変更をロールバックしました。 - 拒否動作:
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 の応答に含まれる数値、ブール値、配列も同様です)。キーを設定するuci_applyのドライランでは、そのキーがマスクされ、ステージングされた内容は何も残りません。network_clientsとlogreadの出力は、信頼できないテキストを示すマーカーで始まります。秘密情報を設定していないクライアントから誤ったmfa_unlockコードを 5 回入力すると、5 分間ロックアウトされます(6 回目は再試行時刻を示して拒否されます)。監査ログには最初の 4 回がERROR、続いてDENIEDと記録され、入力したコードは一切記録されません。CSI と OSC シーケンス、カラーコード、BEL、双方向テキスト上書き制御文字(いずれもルーター自身のログに存在)を含む syslog 行をubus call log writeで書き込むと、logreadからはテキストとタブだけが返されました。また、長すぎる行は 1024 バイトで切り詰められ、[+N bytes]という注記が付きました。ルーターから取得したuci changesの構文(+=、-=、'\''、削除を示す単独の-path)は、テスト用フィクスチャとして保存されています。 - 信頼性の高い変更(1.2.0)、同じボードでデーモンの stdio ブリッジ経由で実行:
fw4 checkは uci にステージングされた変更を検出します(無効な値でも終了コードは 0 で、[!]行を出力します。これを検証で読み取ります):不正なゾーン値を設定するドライランは新たな問題として報告され、実際の適用は拒否され、ステージングされた内容もスナップショットも残りません。古いexpected_revisionsはCONFLICTとなり、現在の値は通過します;呼び出しで変更しない設定を指定すると拒否されます。LAN インターフェースでのドライランは管理経路を明示し、プローブなしの実際の適用は、何もステージングされずに拒否されます。すでにsystem.ntp.serverにある要素へのadd_listはスキップされます。luciのスクラッチオプションで、pingとresolveのプローブを伴う適用は、リロード後に両方を OK と報告し、ロールバックを有効にします;確認すると履歴エントリーが残ります(0700ディレクトリ、0600ファイル);history=diffは変更で追加された内容を表示します;restoreはファイルをモードも含めてバイト単位で完全に元に戻し、プローブを実行して独自の履歴エントリーを残します;プローブが失敗する適用はロールバックを有効にしたままPROBE FAILEDと報告し、uci_rollbackはファイルをバイト単位で完全に元に戻します。デーモンに対するservice_control restartは、その状態について「Settled after」と報告します。 - 導入と認証情報(1.3.0)、同じボードで実施:
install.shでビルドをインストールし、openwrt-mcp versionは 1.3.0 を報告しました。実際の OpenSSH を使った Windows PC からのconnect doctorは 7 つすべての手順を通過し、23 個のツールを一覧表示しました;ルーターが認識していない鍵では key accepted で停止し、ポートが閉じている場合は ssh reachable で停止し、どちらも対処方法を示しました。Cursor 用のconnect --writeは PC 自身のssh-keygenで鍵を作成し、エントリーを JSON ファイルにマージしました。2 回目の実行ではエントリーに変更がないことが報告されました。diagはバージョン、ボード、ポリシーの形式、監査ログ行を表示し、SSH クライアントのアドレスをip-1として示し、監査引数は表示しませんでした;--detailを指定するとマスクされた要約も追加されました。sysupgrade backupは-rw-------のアーカイブを作成しました(4.3 MB。すでに存在するファイルのモードはsysupgrade -bが保持します)。2 回目のバックアップでは最初のバックアップが削除されました。再起動直後のデーモンの常駐メモリは約 10 MB でした。 - セッションのクリーンアップ(1.3.0)、同じボードで実施: デーモンの再起動後、新しいビルドで起動した stdio ブリッジはすぐに終了し、クライアントの次の呼び出しではエラーなく新しいセッションが開かれました。以前のビルドで動作を続けているブリッジは、クライアントから次のリクエストが来るまでそのまま動作しました。
statusは期限切れの許可 45 件を 1 行にまとめて表示しました;pruneはそれらを削除し、prune監査エントリーを 1 件書き込み、デーモンは有効な許可だけを読み込んで再起動しました。 - 診断(1.4.0)、同じボードで
@readonlyクライアントを使用:system_status mode=doctorは、マージ待ちの.apk-newファイル 3 件という低レベルの指摘を 1 件一覧表示しました。これは実際に存在し、誤った指摘はありませんでした。mode=auditは WAN からの WireGuard ポートを許可するファイアウォールルール(存在し、意図どおり)と、すべてのアドレスで待ち受けるuhttpd(実際にそのとおり)を一覧表示し、それぞれに Wiki のリンクと次に実行する呼び出しを示しました。logread mode=summaryは 1,000 行強を約 50 件の異なるメッセージにまとめました。ベースラインを保存し、定期ジョブの実行後に比較すると、稼働時間のフィールドが変わるため、モニタリング行が毎回新しい項目としてカウントされました。これが時間を<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 を読み取りました。ドライランの警告と WireGuard の書き込み機能は、テスト専用に作成した使い捨てインターフェースで実行しました(LAN でも実際のトンネルでもありません)。その後、インターフェースは削除しました:ロールバックを有効にした経路でピアを追加および削除し、ロールバックによってピアが復元されることを確認しました。openwrt-mcp callと--toolset diag --read-onlyを指定したセッションは、PC から SSH 経由で実行しました。実機テストでは、最後のピアではないピアを削除した際に誤ってNOT_APPLIEDとなるケースが見つかりました(UCI の匿名セクション ID は位置ベースのため、読み戻し時に誤ったセクションを参照していました)。これは修正済みで、テスト用のフェイクも UCI と同様に ID を振り直すようになりました。 ユニットテストとエンドツーエンドテスト(インメモリトランスポートおよびブリッジのハンドシェイクを介した実際の MCP クライアント)は、任意の OS でフェイクルーターを対象に実行されます。対象となるのは、適用/確認/ロールバック/タイムアウト、ウィンドウ内の再起動、ドライラン、外部でステージされた編集の拒否、操作一覧、キャプチャした 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 はフェイクルーターを対象に実行し、ログサマリーで確認したログは1台のルーターのものだけです。WireGuard の引き継ぎ時のファイルモードは、CI で Linux 上のアサーションを行っています。他のボードからの報告も歓迎します。
クレジット
-
GlassOnTin/openwrt-mcp(Ian Williams、MIT)--
このプロジェクトの基盤です。ここから引き継いだのは、ルーター上で動作する procd 管理の静的 Go デーモン、スコープ glob によるデフォルト拒否ポリシー、レート制限と有効期限、ダイジェストとして保存するベアラートークン、TOTP ウィンドウ、秘密情報を伏せ字にする JSONL 監査ログ、ループバック専用 HTTP、ubus 応答の整理、およびuci_applyの確認またはロールバックの仕組みです。上流プロジェクトの Git 履歴はこのリポジトリに引き継がれています。 -
jsebgiraldo/openwrt_ssh_mcp --
アイデアの参考として確認した、ルーター外で動作する Python サーバーです(呼び出しごとに SSH 接続し、正規表現でコマンドを許可リスト化)。ここから4つを採用し、再実装しました。- ファームウェアイメージの書き込み前検証(
sysupgrade -T->sysupgradeアクションtest); - ファームウェアとボードの情報(
system_statusの一部); - パッケージ管理(
apk向けに書き換え:pkg_query、pkg_change); - ping/traceroute/nslookup(
net_diag)。
採用しなかったもの:OpenThread ボーダールーター用ツール(ハードウェア固有)、
opkgとiptablesのコマンド(25.12 では廃止)、ファームウェア書き込みツール(取り消し不能で、リモートからの復旧も不能)、シェル文字列の正規表現による許可リスト化(代わりに argv とポリシースコープを使用)。 - ファームウェアイメージの書き込み前検証(
貢献とセキュリティ
バグ報告とプルリクエストを歓迎します。CONTRIBUTING.md をご覧ください。脆弱性は公開イシューではなく、SECURITY.md の説明に従って非公開で報告してください。予定されている作業は ROADMAP.md に記載しています。
バグの報告: ルーター上で 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 サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。