追記(2026-07-27): その後この構成を使い込むうちに、土台にしているvibecode-agentの開発が2026-05-14を最後に止まっていることに気づき、まったく別方式のMCPであるha-mcpへ一本化しました。両者を実機で比較した検証記をHA用MCPは2系統ある。実機で比較してha-mcpに寄せた理由に書いています。本記事の手順は当時の記録としてそのまま残しますが、これから新しく組むなら、先にそちらを読んで方式を選ぶことをおすすめします。
はじめに
Home Assistant(以下HA)を使っていると、automationやdashboard、themeをYAMLで手書きする場面がどうしても出てきます。慣れれば書けるものの、「エアコンを23時に消すautomationを作って」くらいの要件でエディタを開いてYAMLと格闘するのは、正直だるい。
そこで、HAの設定を自然言語でClaude Codeに指示して作れる環境を組みました。使ったのは次の2つです。
- home-assistant-vibecode-agent — HA側で動くAgent(アドオンとして導入)
@coolver/home-assistant-mcp— ローカルで動くMCPサーバー
ゴールは「Claude CodeからMCP経由でHAの実データ(今回は250エンティティ)を取得でき、automation等を作れる状態」です。
本記事は、Home Assistantを使っていてClaude CodeやCursorなどのAI IDEから自動化を作りたい人向け。Docker / DNSの基礎が分かる程度を想定しています。詰まった箇所とその切り分けを一次情報として残すのが主眼なので、そこが本題だと思って読んでください。
全体アーキテクチャ
なぜコンポーネントが2つに分かれているのか。ひとことで言うと、Agentは「HAの内部APIを叩く役」、MCPは「IDEとAgentを仲介する役」です。IDEを直接HAに触らせず、AgentのAPI越しに操作する設計になっています。
[ローカル (WSL2)]
Claude Code
│ MCP (stdio)
▼
@coolver/home-assistant-mcp (npx で起動)
│ HTTPS + Bearer(Agent Key)
▼
[Cloudflare Tunnel] ha-agent.example.com
│
▼
[Home Assistant (HA OS)]
Cloudflared アドオン
│ 内部ネットワーク
▼
vibecode Agent (:8099)
│
▼
HA Core / entities
- MCP → Agent は HTTPS(Cloudflare Tunnel)+ Bearer(Agent Key)。
- Cloudflaredアドオンがトンネルを張り、内部でAgent(
:8099)へルーティングします。
前提環境
| 項目 | 値 |
|---|---|
| HA | Home Assistant OS / Supervised(アドオンストアが使える) |
| ローカル | WSL2(Windows上のLinux) |
| Node | v20+(今回はv24) |
| ツール | Claude Code CLI, npx, Cloudflaredアドオン(brenner-tobias) |
なぜAdd-onモードを選んだかというと、HA OSならアドオンストアからワンクリックで入るのが一番手軽だからです。Standalone Dockerモードもありますが、今回は不要でした。
Part A: Agentアドオンの導入
ここは公式手順どおりで詰まりません。
- 設定 → アドオン → アドオンストア → 右上「⋮」→ リポジトリ に以下を追加
https://github.com/coolver/home-assistant-vibecode-agent - HA Vibecode Agent をINSTALL
- 「起動時に開始」をONにしてSTART
- Open Web UI から、自動生成された Agent Key を控える
- AgentのAPIは
:8099。ヘルスチェックは/api/health(認証不要)
Agent Keyはこのあと繰り返し使うので、安全な場所に控えておきます。
Part B: Cloudflare TunnelでAgentを公開
前提として、すでにCloudflaredアドオンでHA本体(:8123)を公開済みです。そこに Agent(:8099)用のホスト名を1つ追加します。
ここで重要な分岐があります。Cloudflaredには2つの運用モードがあり、ホスト名の追加方法がまったく違います。
| モード | 見分け方 | ホスト名の追加場所 |
|---|---|---|
| アドオン管理 | 設定に external_hostname あり / tunnel_token なし |
アドオン設定の additional_hosts |
| ダッシュボード管理(今回これ) | 設定に tunnel_token あり |
Cloudflare Zero Trustダッシュボード |
今回は tunnel_token 方式だったので、ダッシュボードでPublic Hostnameを追加します。この方式では additional_hosts は効きません。ここが最初の落とし穴でした。アドオン設定をいくらいじってもホスト名が生えてこないので、モードの見極めを最初にやるのが正解です。
手順(ダッシュボード管理の場合)
- https://one.dash.cloudflare.com → Networks → Tunnels → 該当トンネルを開く
- Public Hostname → Add a public hostname
- Subdomain:
ha-agent - Domain:
example.com - Type: HTTP(Agentは平文HTTPを話すため)
- URL:
<agentの内部ホスト名>:8099
- Subdomain:
- Save(DNSのCNAMEも自動で作成される)
IPか、内部ホスト名か
最初は service: http://<HAのLAN IP>:8099 にしようとしました。ところがHAのIPはDHCPで固定していないため、IPが変わった瞬間にこの経路が壊れます。
解決策は、IPではなくアドオンの内部ホスト名を使うこと。CloudflaredとAgentは同じ内部ネットワーク上にいるので、内部ホスト名で名前解決すればIP変動の影響を受けません。
内部ホスト名を調べるには、まずAgentアドオンのフルslug(識別子)が必要です。HAのターミナルアドオン等から ha apps の一覧を絞り込んで確認します。旧名・新名どちらでもヒットするよう、キーワードは広めに指定しておくと確実です。
# 旧名(cursor)/新名(vibecode)どちらでもヒットするよう広めに
ha apps | grep -iE 'vibecode|cursor|agent'
ℹ️ このアドオンは旧名
home-assistant-cursor-agentで始まり、表示名だけが「HA Vibecode Agent」に変わったため、slugにはcursorが残っています(例:xxxxxxxx_home_assistant_cursor_agent)。vibecodeだけで絞ると引っかからないことがあるので注意してください。
見つかったフルslugを渡して、内部ホスト名(hostname フィールド)を取り出します。
# <フルslug> は上で確認した値に置き換える
ha apps info <フルslug> | grep hostname
得られる内部ホスト名は、slugのアンダースコアをハイフンに置換した形(例: xxxxxxxx-home-assistant-cursor-agent)になります。この値を、Part Bのダッシュボード手順の URL 欄に <内部ホスト名>:8099 として指定すればOKです。
疎通確認
curl https://ha-agent.example.com/api/health
# => {"status":"healthy","version":"2.10.47",...}
healthy が返れば、トンネル → Agentまでの経路は通っています。
Part C: Claude CodeにMCPを登録
MCPパッケージは @coolver/home-assistant-mcp。Claude Codeは mcpServers 形式で登録します。
claude mcp add home-assistant \
-s local \
-e HA_AGENT_URL=https://ha-agent.example.com \
-e HA_AGENT_KEY=<YOUR_AGENT_KEY> \
-- npx -y @coolver/home-assistant-mcp@latest
-s local は、このプロジェクト専用のローカル設定に保存する指定です(キーを共有ファイルやリポジトリに置かないため)。実体は ~/.claude.json のプロジェクトエントリに書かれます。
JSONで書くと等価なのは次の形です。
{
"mcpServers": {
"home-assistant": {
"command": "npx",
"args": ["-y", "@coolver/home-assistant-mcp@latest"],
"env": {
"HA_AGENT_URL": "https://ha-agent.example.com",
"HA_AGENT_KEY": "<YOUR_AGENT_KEY>"
}
}
}
}
接続確認:
claude mcp list
# => home-assistant: ... - ✔ Connected
セッション再起動が要る
ひとつ注意。セッションの途中で claude mcp add した場合、claude mcp list は Connected になりますが、IDE側でMCPツール(mcp__home-assistant__*)が実際に使えるようになるのは Claude Codeを再起動した後です。
再起動前に動作検証だけしたいなら、Agent Keyを使った直接 curl(例: /api/entities/list)で代替できます。
ハマりどころと切り分け(本題)
ここが本記事の目玉です。
症状: ホスト名を追加した直後、ブラウザで DNS_PROBE_FINISHED_NXDOMAIN。ローカル(WSL)からも名前解決できない。
「ダッシュボードの設定を間違えたか?」と疑いたくなりますが、慌てて設定を触る前に、どのレイヤーで壊れているのかを切り分けます。手順はそのまま再現できます。
1. 公開DNSでは解決するか?
Cloudflareのdns-query(DoH)に直接問い合わせて、そもそもレコードが公開DNSに存在するか確認します。
curl -s -H 'accept: application/dns-json' \
"https://1.1.1.1/dns-query?name=ha-agent.example.com&type=A"
# Status:3 = NXDOMAIN(レコード未作成) / Status:0 = 解決OK
Status:0 なら、公開DNS上はレコードが存在している=Cloudflare側の設定は正しい、と切り分けられます。このとき、レスポンスJSONの Answer 配列に実際のAレコード(CloudflareのIP、例: 104.21.42.219)が入っています。次のステップで使うので控えておきます。
2. エンドポイント自体は生きているか?
ローカルのDNSを迂回して、CloudflareのIPへ直接叩きます。IPは先ほどステップ1のレスポンスで得た値を --resolve に渡します。
# 104.21.42.219 の部分はステップ1のAnswerで得たCloudflareのIPに置き換える
curl --resolve ha-agent.example.com:443:104.21.42.219 \
https://ha-agent.example.com/api/health
これで healthy が返れば、エンドポイントは生きている=問題はローカルの名前解決だけに絞り込めます。
3. 原因はISPリゾルバのネガティブキャッシュ
公開DNSは正常、--resolve での直接アクセスも正常、なのにローカルだけ解決できない。これは典型的なネガティブキャッシュです。
一度NXDOMAIN(存在しない)を引くと、その「存在しない」という結果がSOAレコードのminimum値に従って最大30分ほどキャッシュされます。ホスト名を作る前に一度アクセスしてしまうと、この否定結果を掴んでしまうわけです。
厄介なのは、ipconfig.exe /flushdns(Windows側)を叩いても消えないことがある点。キャッシュがWindowsやWSLではなくISP側のリゾルバにあるからです。
対処は次のいずれか。
- (a) 数十分待つ(キャッシュのTTL切れを待つ)
- (b)
/etc/hostsに暫定エントリを書く - (c) 使うリゾルバを
1.1.1.1などに変える
今回は待って自然回復させました。実測でおよそ10分で解決しています。同じ環境(WSL2 + 家庭用ISP)の人には、この「公開DNSは正常なのにローカルだけNXDOMAIN → 原因はISPのネガティブキャッシュ」という筋道がそのまま刺さるはずです。
セキュリティの考慮
ここは必ず押さえておきたい点です。
Agentは、HAの設定をread-writeで全操作できます。automationの作成、config書き換え、アドオン操作まで、OpenAPIにそれらのエンドポイントがずらりと並びます。それをCloudflare Tunnelで公開し、防御がBearerキー1枚だけという構成は、便利な反面リスクがはっきりあります。キーが漏れれば、家じゅうの設定を書き換えられる入り口になり得るということです。
推奨は、Cloudflare Access(サービストークン)を前段に被せることです。ただし注意があります。npxで動くMCPクライアントは、Accessのサービストークンヘッダ(CF-Access-Client-Id / CF-Access-Client-Secret)を自動では送出しません。そのためAccessを有効化するとMCPが403になります。MCP側にトークンを通す手当てが別途必要で、ここは今後の課題として残しています。
なお本記事では、公開ホスト名を ha-agent.example.com のダミーのままにしています。実ホスト名を載せると、この記事がそのまま攻撃者へのヒントになりかねないからです。Agent Keyも当然マスクしています。自分で書くときも、公開範囲には気をつけてください。
動作確認 / できるようになったこと
接続状態:
claude mcp list
# => home-assistant: ✔ Connected
Agent API越しに実データを取得してみます。
curl https://ha-agent.example.com/api/entities/list \
-H "Authorization: Bearer <YOUR_AGENT_KEY>"
今回の環境では、総 250エンティティ(sensor 168, binary_sensor 18, update 15, switch 9 …)が返ってきました。ここまで来れば、「エアコンを23時に消すautomationを作って」のような自然言語の指示から、Claude CodeがMCP経由で実データを見ながら設定を組める状態です。
まとめ
Add-on + Cloudflare Tunnel + MCPの3点で、Claude CodeからHAを自然言語で管理できるようになりました。実際に効いたポイントを整理します。
- Cloudflaredが token方式なら、ホスト名追加は
additional_hostsではなくダッシュボードで行う。 - HAがDHCPなら、serviceはIPではなくアドオンの内部ホスト名を指定する。
- DNS追加直後のNXDOMAINは、まずISPのネガティブキャッシュを疑う(公開DNS /
curl --resolveでレイヤーを切り分ける)。 - セッション途中でMCPを追加したら、Claude Codeの再起動でツールが有効化される。
次にやりたいのは、Cloudflare Accessでのハードニングと、実際のautomation作成例の記事化です。そこまでやると「AIにHAを任せる」構成として一段完成度が上がるはずなので、続きはまた書きます。
追記(2026-07-27): この「続き」は、ハードニングではなく構成そのものを見直す話になりました。使い込むうちに「これはこのMCPではできないのでは」という場面が出てきたので別方式のMCPも入れて比較したところ、土台のvibecode-agentが開発停止していることに気づき、最終的に乗り換えています。→ HA用MCPは2系統ある。実機で比較してha-mcpに寄せた理由
付録: 使った主なコマンド一覧
# 内部ホスト名の確認(HAターミナル)
ha apps | grep -iE 'vibecode|cursor|agent' # フルslugを確認(旧名cursorが残る)
ha apps info <フルslug> | grep hostname # 内部ホスト名(slugの _ を - に置換した形)
# 疎通(トンネル越し・認証不要)
curl https://ha-agent.example.com/api/health
# 公開DNSの確認(DoH)
curl -s -H 'accept: application/dns-json' \
"https://1.1.1.1/dns-query?name=ha-agent.example.com&type=A"
# ローカルDNSを迂回して実エンドポイント確認
curl --resolve ha-agent.example.com:443:<CF_IP> \
https://ha-agent.example.com/api/health
# MCP登録・確認
claude mcp add home-assistant -s local \
-e HA_AGENT_URL=https://ha-agent.example.com \
-e HA_AGENT_KEY=<YOUR_AGENT_KEY> \
-- npx -y @coolver/home-assistant-mcp@latest
claude mcp list
