Muse Secure VMに自作サーバーを置いて、M5Stack StopWatchから接続できるか試した

Muse Secure VMに自作サーバーを置いて、M5Stack StopWatchから接続できるか試した

Muse Secure VMに自作の中継サーバーを置き、OpenAI Realtime APIへ接続する構成を試しました。VM内のサーバーまでは起動できたものの、StopWatchからのアクセスが403で拒否され、音声中継の実現には至りませんでした。その検証記録をお届けします。
2026.11.12

はじめに

前回は、Muse Gadget SDK標準のリアルタイム音声通話をM5Stack StopWatchで試しました。Muse Secure VM(以下、VM)への接続は成功したものの、通話開始のPOST /voice/realtimeがHTTP 404となり、音声会話には進めませんでした。

https://dev.classmethod.jp/articles/shuntaka-muse-gadget-sdk-m5stack-stopwatch-voice/

そこで、Muse Secure VMに自作の中継サーバーを置き、OpenAI Realtime APIへ接続する方法を試しました。VMへ接続できるなら、その中で動くサーバーにも音声を渡せるのでは、と考えたためです。

試した構成

StopWatchが録音・再生を担当し、OpenAIへの接続や会話の制御をVM側へ移す構成です。モデル・声・指示文の変更を、端末のファームウェアを書き換えずに試すことを狙いました。

Museアプリとのペアリング、VM認証、自作サーバーへの接続を検証した構成図

ペアリングとVM接続に使う3種類のトークンは、図の1から4の順に受け渡します。接続確認には、認証情報を保存済みのStopWatchを使いました。

自作サーバーはVM内の127.0.0.1:8787で起動しました。8787は今回選んだ待ち受けポートで、Muse指定の番号ではありません。既存のMuseデーモンや公式ルートは変更していません。

用途 パス
Muse公式の音声通話 POST /voice/realtime
自作サーバーの音声中継 WebSocket /realtime
自作サーバーのecho確認 GET /probe/echo?nonce=...

VM内にサーバーを用意する

echo確認とOpenAI音声中継を、1つのサーバーに実装しました。

https://github.com/shuntaka9576/muse-realtime-bridge

MuseへリポジトリのURLを渡してセットアップを依頼しました。OpenAIのAPIキーは専用入力画面からMuseの認証情報ストアへ登録し、実キーをチャットや中継の.envに保存しない方法を指定しました。

Museへ中継のセットアップと認証情報ストアの利用を依頼した画面

OpenAI APIキーの発行とMuseへの登録

OpenAI Platformで検証用のAPIキーを発行しました。有効期限は1日にしています。

OpenAI Platformで検証用APIキーを発行する画面

今回はPermissionsを「All」のまま発行しましたが、継続して使うキーは「Restricted」で必要な権限に絞るのが望ましいです。

Museが提示したOpenAIコネクターの「連携する」から、認証情報ストアへ登録しました。

Museによるセットアップの途中経過とOpenAI認証情報の登録ボタン

中継は代替トークンを使い、外部通信時にSentinelが実キーへ置き換えます。

https://muse.ai/blog/security-and-safety-for-ai-agents-our-approach-with-muse

検証結果

VM内のechoとOpenAI接続は成功

VM内では、次のcurlでechoも確認しました。

curl --fail 'http://127.0.0.1:8787/probe/echo?nonce=0123456789abcdef0123456789abcdef'

HTTP 200と、送信したものと同じnonceが返りました。

Museからは、この方式でPython SDKのWebSocket接続と音声往復に成功したとの報告がありました。ここで確認できたのは、VM内の中継からOpenAIまでの通信です。

StopWatchから接続すると403になる

Gadget SDKに、黄ボタンでecho確認を実行する検証モードを追加しました。CONFIG_MUSE_VM_PROBE=yでビルド・書き込みし、ペアリング済みの端末から既存の認証済みVM接続を使ってリクエストを送ります。

https://github.com/shuntaka9576/muse-gadget-sdk/tree/debf9646368ad61f6dc1d45c989f9e8e9f94fd5a

https://github.com/shuntaka9576/muse-gadget-sdk/blob/debf9646368ad61f6dc1d45c989f9e8e9f94fd5a/esp32/components/muse/muse_vm_probe.c

端末の画面には、次の結果が表示されました。VM内でサーバーが起動していることを確認しても、結果は変わりませんでした。

Noise接続成功後、echo確認でHTTP 403となったStopWatchの実機画面

中継まで届いているかをログで切り分ける

403だけでは、どこで拒否されたか分かりません。Museに中継サーバーの到達ログを追加してもらい、VM内のcurlが記録されることを確認してから、StopWatchの黄ボタンをもう一度押しました。このログ追加はVM内だけの診断用変更です。

StopWatchからのリクエストが中継サーバーへ届いていないことを確認したMuseの報告

Museから共有されたログの確認結果では、記録されたのは対照アクセスだけでした。自作echoも403を返す実装ではないため、今回のリクエストは中継へ届く前に拒否されたと判断しました。

VMへの接続と、自作サーバーへの転送は別だった

SDKの送信処理では、アプリリクエストをNoise上のDaemonサービス宛てに送ります。

https://github.com/shuntaka9576/muse-gadget-sdk/blob/debf9646368ad61f6dc1d45c989f9e8e9f94fd5a/esp32/main/noise_control.cpp#L1125-L1150

/probe/echoを指定しても、VM内の127.0.0.1:8787へ接続する指示にはなりません。

Museに転送方法も調べてもらいましたが、CLI・VM内の設定ファイル・ドキュメントから、独自サーバーへ転送する設定手段は見つかりませんでした。403を返した内部のルールやログは参照できていないため、「未許可のルートだから拒否された」という詳細までは確認できていません。

今回分かったのは、MuseのVM認証を通過しても、任意のVM内プロセスにアクセスできるわけではない、という点です。サーバーの起動やポート番号の変更だけでは、この接続経路は作れませんでした。

常駐化にも制約があった

サーバーの削除を依頼した際、既にプロセスが消えていたとの報告があり、常駐化できなかった理由をMuseに確認しました。

サーバー削除時に、プロセスの停止と常駐化の制約を確認したMuseとのやりとり

接続経路とは別に、サーバーを動かし続ける点でも問題がありました。Museに常駐化を依頼しましたが、systemctl --userはFailed to connect to busとなり、ユーザーサービスを登録できませんでした。代わりにnohupで起動したプロセスも翌朝には停止していました。

MuseからはVMの入れ替えでプロセスが失われるとの説明がありましたが、今回の停止原因は未特定です。継続稼働や自動復旧の方法も確認できておらず、常時利用する中継サーバーの配置先としては、この点も課題になりました。

さいごに

今回は、Muse Secure VMに自作APIを置き、StopWatchから音声を渡して会話する構成を試しました。VMへの接続とVM内でのAPI実行はできたものの、その間をつなぐ転送経路を用意できず、音声会話までは実現できませんでした。

このような用途では、Muse Secure VMに中継を任せるのではなく、端末からOpenAIへ直接接続する実装も選択肢になります。その場合、長期有効なOpenAI APIキーを端末に持たせるなら、端末からキーを取り出されるリスクも考える必要があります。

もちろん、自分で中継APIと端末向けのトークン発行・認証を用意し、OpenAIのキーをそのサーバー側で管理する方法もあります。ただ、その分サーバーの運用も必要です。今回はそこをMuse Secure VMに任せられないかと考えましたが、調べた範囲では難しい、という結果になりました。

この記事をシェアする

関連記事