📺

SONY BraviaをAIで操作!MCPとAgent Development Kit(ADK)を使ったAIエージェント開発

に公開

MCPを使った何か実用的なAIエージェントを作成したいなと思い立ち、家のテレビを自然言語で操作するAIエージェントをAgent Development Kitを使って作成しました。MCPの部分はTypeScriptのライブラリを使いましたが、TypeScriptよくわからないマンなので、コードはほとんどCursorに書いてもらいました。

↓こんな感じのものができました。ADK組み込みのUIを使っているので、日常生活に組み込むにはもう少し開発が必要になりそうです。

ソースコードは下記のリポジトリにアップしています。
https://github.com/hiro88hyo/adk_tv_control

設計方針について

今回のSONY BraviaコントロールMCPおよびAIエージェントの開発は、テレビを操作する部分をMCPで開発し、ユーザーリクエストに基づき、MCPをツールとして使う部分をADKで開発しました。

ローカルの環境に手を入れたくなかったのでコンテナで開発しています。

あくまでも家庭内での利用を想定しているので、コンテナのマルチステージビルドやセキュリティを十分に考慮していません、その辺はご了承ください。

テレビ操作MCPの設計

SONYのBraviaはREST APIを持っており、JSON-RCP経由で操作ができます。
https://pro-bravia.sony.net/ja/develop/integrate/ip-control/index.html

法人向けとあるが、我が家のBravia(KJ-49X9500G)も2025年5月現在対応していました。

具体的な操作方法については下記のエントリーなどを参考にしてください。
https://zenn.dev/ryder472/articles/86e4c7c3f06221

実装は、TypescriptのMCPライブラリを使用しました。
https://github.com/modelcontextprotocol/typescript-sdk

AIエージェント (ADK) の詳細設計

MCPを操作するAIエージェントには、GoogleのAgent Development Kit (ADK) を使用し、ユーザーが自然な言葉でテレビを操作できることを目標としました。

https://google.github.io/adk-docs/

各機能の実装について

作った物の中身を少々解説します。

MCPの実装

基本的にはexpressでWebサーバを立ち上げ、HTTPのリクエストを受け付ける方式にしました。STDINで動作する実装をネットでよく見ますが、dockerコンテナにしたのでhttp通信を使います。認証を入れてないのでインターネットに公開するのはやめましょう、家のテレビを操作されてしまいますw

MCPの通信方式について

詳しくは下記リンク先などを確認していただくとして、だいたい以下のような実装。Streamable HTTPは2025-03-26のMCPの仕様なので、ADKはまだStreamable HTTPには対応していなかった(バージョン1.0時点)。リポジトリのソースはHTTP+SSEおよびStreamable HTTPの両方に対応しており、コマンドラインオプションで切り替え可能としました。

  • HTTP+SSE(Server-Sent Events)トランスポート:'/sse'のようなGETのエンドポイントで接続を確立し、'/message'のようなエンドポイント(GETから知らされる)にJSON-RPCを投げる方式。クライアントごとに接続を維持する(ステートフル)必要がある。
  • Streamable HTTP:セッションIDを使って接続を維持していなくても会話を維持できる。状況に応じてストリーミングに切り替え可能。

https://zenn.dev/ks0318/articles/f92589946cd344

MCP Inspectorについて

nodeではMCP Inspectorという、MCPサーバーのテストツールがあるので、これを使うとデバッグしやすいです。

npx @modelcontextprotocol/inspector

エージェントの実装

AIエージェント実装のキモの部分です。エージェントにはGoogleのAgent Development Kit(ADK)を使いました。ちなみに先日発表されたAgent2Agentプロトコル(A2A)とADKは厳密には違う物です。A2Aを理解しなくてもエージェントだけならADKで作成可能です。

ADKでエージェントを作る

ADK組み込みのエージェントを使うだけなら手順とソースはとても簡単です。
まずルートのディレクトリにエージェントのフォルダを作ります。今回であれば'tv_control_agent'という名前のフォルダを作ります。後述するADK組み込みのUIがディレクトリをエージェントのディレクトリの__init__.pyを見に行きます。

from . import agent

agent.pyがエージェントの実体です。
agent,pyでは、具体的なエージェントを記述していきますが、実際に書いたのはこれだけです。

root_agent = LlmAgent(
  model=LLM_MODEL,
  name="bravia_control_agent",
  instruction=system_instruction,
  tools=[
    MCPToolset(
      connection_params=SseServerParams(
        url=MCP_SERVER_URL,
        headers={'Accept': 'text/event-stream'}
      ),
    )
  ]
)

toolsにAIがコールするtoolを記述します。今回は上で作ったMCPサーバのアドレスを指定すると、MCPが使えるツール名や機能を自動で取得します。

最終的なディレクトリ構造は下記のようになりました。

.
├── Dockerfile
├── entrypoint.sh
├── pyproject.toml
├── .python-version
├── README.md
├── tv_control_agent
│   ├── agent.py
│   ├── .env
│   └── __init__.py
└── uv.lock

プロンプト

system_instructionに具体的なプロンプト書いていくのですが、ここが心臓部と言っても過言ではないでしょう。ここをキチンと書くことがAIエージェント開発の勘所になります。Geminiに作ってもらいましたw

プロンプト(長いので畳みます)

  # あなたのタスク

  - ユーザーの指示から、操作の意図(例:電源操作、チャンネル変更、音量調整など)を正確に把握してください。
  - 指示に含まれる重要な情報(例:チャンネル番号、音量レベルなど)を抽出してください。
  - 把握した意図と抽出した情報に基づいて、呼び出すべき最適なMCPサーバー機能を特定してください。
  - 特定した機能を、適切なパラメータと共に実行するように指示してください。
  - 操作を実行した後、ユーザーに簡潔に結果を報告してください。(例:「テレビをつけました」「チャンネルを5に変更しました」)
  - 指示が曖昧な場合や、必要な情報が不足している場合は、ユーザーに確認を求めてください。(例:「どのチャンネルに変更しますか?」)
  - MCPサーバーの機能で対応できないリクエストについては、その旨を丁寧に伝えてください。

  # 利用可能なMCPサーバー機能:

  - getSystemInformation(): テレビのシステム情報を取得します。
    例:「テレビの情報を教えて」「システム情報を表示して」
  - getPowerStatus(): テレビの現在の電源状態(オン/オフ)を取得します。
    例:「テレビついてる?」「電源の状態は?」
  - setPowerStatus(status: "on" | "off"): テレビの電源状態を設定します。
    status="on": 電源をオンにします。
    status="off": 電源をオフにします。
    例:「テレビつけて」「電源オフにして」
  - getContentList(source?: string): 視聴可能な番組やコンテンツのリストを取得します。オプションでソース(例:"地上波", "BS", "CS")を指定できます。
    TVの場合はsourceに'tv:isdbt'を指定します。BSの場合は'tv:isdbbs'、CSの場合は'tv:isdbcs'です。
    例:「番組表見せて」「どんなチャンネルがある?」「BSの番組リストを教えて」
  - setPlayContent(channel: number | string, source?: string): 指定されたチャンネルまたはコンテンツIDの番組を再生します。チャンネル名で指定された場合は、適切なチャンネル番号に解決を試みてください。
    uri: getContentListで取得できるuri
    例:「5チャンネルにして」「NHKに変えて」「BS朝日にして」
  - getVolumeInformation(): テレビの現在の音量情報を取得します。
    例:「今の音量は?」「ボリューム教えて」
  - setAudioVolume(number): テレビの音量を設定します。
    volume: 絶対値 (0-100) 。
    例:「音量上げて」「ボリューム30にして」「ミュートして」「音を少し小さくして」

  # 指示解釈のヒント:

  - 「テレビを消して」→ setPowerStatus(status="off")
  - 「日テレが見たい」→ setPlayContent(uri="tv:isdbt?trip=32738.32738.1040&srvName=日テレ1")
  - 「音量を10下げて」→ setAudioVolume(volume=15)
  - 「今のチャンネルは?」→ (直接的な機能はないが、もし可能なら直前の setPlayContent の情報や、getSystemInformation などから類推を試みるか、「チャンネル情報を取得する機能は現在ありません」と回答)
  - 「静かにして」→ setAudioVolume(volume="mute") または setAudioVolume(volume=10) など文脈に応じて判断。

  # あなたの応答例:

  - ユーザー: 「テレビをつけて」 AI: (内部処理: setPowerStatus(status="on") を呼び出し) → 「テレビをつけました。」
  - ユーザー: 「音量上げて」 AI: (内部処理: setAudioVolume(volume=15) を呼び出し) → 「音量を15にぢました。」
  - ユーザー: 「電気消して」 AI: 「テレビの電源操作は可能ですが、お部屋の照明を操作する機能は現在ありません。」
  - 上記を参考に、ユーザーにとって最高のテレビ操作体験を提供してください。

エージェントを動かす

ルートディレクトリでadk webすることで、adk組み込みのチャットウインドウが起動します。ここにアクセスすることで、実際にエージェントを動かすことができます。そうやって動かしたエージェントが冒頭の画像になります。

$ adk web --host=0.0.0.0 (デフォルトは127.0.0.1だが、外部アクセスのために変更)
INFO:     Started server process [12]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
 
+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://localhost:8000.                         |
+-----------------------------------------------------------------------------+

エージェントとMCPの通信内容も確認できるのでデバッグに便利です。
実際にLLMがコールするjsonがmcpのスキーマと合わなくて少しハマりました。

マルチコンテナ環境の構築

同一環境にnodeとpython両方入れてもいいのですが、せっかくなので独立したコンテナとして起動するようにしました。

services:
  bravia_mcp:
    build:
      context: .
      dockerfile: bravia_mcp/Dockerfile
    network_mode: host
    ports:
      - 3000:3000
    init: true
  tv_control_agent:
    build:
      context: .
      dockerfile: tv_control_agent/Dockerfile
    network_mode: host
    ports:
      - 8000:8000
    init: true

ポイントは、コンテナからコンテナの外にあるテレビにアクセスさせるために、network_modehostにしていることです、このようにすることで、<dockerを動かしているサーバのIP>:<ポート番号>でアクセス可能になります。ホストマシンとIPアドレスを共有するので、ポート番号の重複には気をつけましょう。

家電操作AIの将来展望

今回の開発を通じて、AIによる家電操作がもたらす未来の可能性を強く感じました。スマートスピーカーの普及により、声で家電を操作することは既に日常の一部となりつつありますが、AI技術の進化はさらにその先へと私たちを導いてくれるでしょう。

  • より高度な自然対話: 「リビングが少し暗いから、いい感じにして」といった曖昧な指示でも、AIが部屋の明るさセンサーや時間帯、ユーザーの好みを学習・考慮して、照明を適切に調整してくれるようになるかもしれません。
  • プロアクティブな家電制御: ユーザーの行動パターンやスケジュールを学習し、「もうすぐ映画の時間ですね。テレビをつけてリビングの照明を少し暗くしましょうか?」といったように、AIが先回りして提案・操作してくれる未来が考えられます。
  • 連携によるスマートホームの実現: テレビだけでなく、エアコン、照明、カーテン、セキュリティシステムなど、家中のあらゆる家電が連携し、AIが一元的に管理することで、個々のユーザーにとって最適な住環境を自動で構築してくれるようになるでしょう。
  • エネルギー効率の最適化: 家全体のエネルギー消費をAIが監視・分析し、無駄な電力使用を自動的に削減することで、環境負荷の低減にも貢献します。
  • アクセシビリティの向上: 高齢者や身体に障害を持つ方々にとって、声やジェスチャーだけで直感的に家電を操作できるAIアシスタントは、生活の質を大幅に向上させる強力なサポートツールとなります。

もちろん、このような未来を実現するためには、デバイス間の相互運用性の確保、セキュリティやプライバシーの保護、そして何よりもユーザーが安心して使える信頼性の高いAIの開発が不可欠です。しかし、これらの課題を乗り越えた先には、より快適で、より豊かで、より人に優しい生活が待っていると信じています。

Discussion