Docker で動かす Hermes Agent に、Mac 上の自作 BigQuery MCP サーバーをつなぐ:stdio が使えない理由と 421 の罠

Docker で動かす Hermes Agent に、Mac 上の自作 BigQuery MCP サーバーをつなぐ:stdio が使えない理由と 421 の罠

AI エージェント「Hermes Agent」を、Docker コンテナに閉じ込めて、Mac の Desktop アプリから使う構成を作りました(構成と、そのときのハマりどころはこちらの記事に書いています)。

普段の Claude Code では、BigQuery を読むための自作の MCP サーバーを使っています。これを Hermes からも使えるようにしてみました。

ところが、Claude Code と同じ感覚で登録しようとしたら、いくつも罠がありました。

この記事でわかること

  • Hermes 本体が Docker の中にいると、stdio 方式の MCP が使えない理由
  • Mac 側の MCP サーバーを HTTP にして、コンテナから呼ぶ手順
  • 421 Misdirected Request と Cancelled. の罠

かかった時間:相談を始めてから、Hermes に登録できるまで40分ほどでした。

目次

つなぐ MCP サーバー

つなぐのは、BigQuery を読むことしかできない自作の MCP サーバーです。ツールは6つで、SELECT 以外の SQL と、読む量が上限を超えるクエリは実行されません。作り方と守り方は「読み取り専用の BigQuery MCP を自作した記事」に書きました。

Hermes に MCP をつなぐ2つの方式

Hermes の MCP 設定は ~/.hermes/config.yaml の mcp_servers: に書きます。方式は2つあります。

方式 内容 今回
stdio Hermes がサーバーを子プロセスとして起動する ✕
HTTP 起動済みのサーバーに URL で接続する ◯

Claude Code では stdio 方式で登録しています。ところが、今回の Hermes は本体がコンテナの中にいます。stdio だと「Hermes が動いている場所」でサーバーを起動するので、コンテナの中から Mac のファイルや uv、Google Cloud の認証情報が見えず、動きません。

選択肢を比べた

案 内容 評価
A. Mac で HTTP 待ち受け Mac でサーバーを HTTP で起動し、コンテナから接続 採用
B. コンテナにマウント サーバーのコードと認証ファイルをコンテナにマウントして stdio で起動 コンテナに uv はあったので可能。ただし認証情報がエージェントの隣に置かれる

A 案を選んだ理由は3つです。

  • Google Cloud の認証情報を Mac の外に出さずに済む
  • コンテナのイメージや compose を変えずに済む
  • サーバーは読み取り専用でも、クエリは自分の権限で走る。エージェントから認証情報は遠ざけておきたい

「見える範囲を絞る」ために Docker に入れたので、そこに認証ファイルを持ち込んでしまっては本末転倒です。

手順

1. Mac 側で MCP サーバーを HTTP で起動する

サーバーに --http --port オプションを足しました。待ち受けは 127.0.0.1 に固定し、FastMCP の DNS リバインディング保護も有効のままにしています。

def _run_http(port: int) -> None:
    # ADC(本人の Google ID)でジョブが走るため、同一 Mac からの接続に限定する。
    # 待ち受けはループバック固定。Host / Origin は localhost 系と、同じ Mac の Docker コンテナ
    # (Docker Desktop の host.docker.internal)のみ許可する(DNS リバインディング対策)。
    mcp.settings.host = "127.0.0.1"
    mcp.settings.port = port
    mcp.settings.transport_security = TransportSecuritySettings(
        enable_dns_rebinding_protection=True,
        allowed_hosts=[f"127.0.0.1:{port}", f"localhost:{port}", f"host.docker.internal:{port}"],
        allowed_origins=[
            f"http://127.0.0.1:{port}", f"http://localhost:{port}", f"http://host.docker.internal:{port}"
        ],
    )
    mcp.run(transport="streamable-http")
uv run servers/bigquery/server.py --http --port 8790

ポイント

  • host.docker.internal を許可リストに入れないと 421 エラーになります(後述)
  • 待ち受けは 127.0.0.1 のままでも、Docker Desktop for Mac のコンテナからは届きました。サーバーのログを見ると、接続元は 127.0.0.1 になっていました。外のネットワークには公開されません

手で起動していると、サーバーが止まった瞬間に Hermes から使えなくなります。そこで、このコマンドを launchd で常駐させました。plist で RunAtLoad と KeepAlive を有効にして、ログイン時に起動し、落ちたら起動し直すようにしています。この記事の接続も、最初から launchd で動かしているサーバーに対して行いました。

同じタイミングで、以前から使っていた別の BigQuery MCP(MCP Toolbox を使ったもの)は機能が重複していたので廃止し、こちらに一本化しました。

2. コンテナの中で Hermes に登録する

docker exec -it hermes hermes mcp add bigquery --url http://host.docker.internal:8790/mcp
#   Does this server require authentication? [Y/n]: n
#   ✓ Connected! Found 6 tool(s) from 'bigquery':
#   Enable all 6 tools? [Y/n/select]: y
#   ✓ Saved 'bigquery' to ~/./config.yaml (6/6 tools enabled)
#   Start a new session to use these tools.

(出力は抜粋です。実際には、この間に6つのツール名と説明が表示されます)

ポイント

  • Mac の hermes コマンドではなく、docker exec でコンテナの中の CLI を呼ぶ
  • -it を必ず付ける(後述)
  • 認証なしのサーバーなので n

3. 接続を確認する

docker exec hermes hermes mcp test bigquery
#   Transport: HTTP → http://host.docker.internal:8790/mcp
#   Auth: none
#   ✓ Connected (678ms)
#   ✓ Tools discovered: 6

Desktop アプリで新しいセッションを開き、データセットの一覧を頼んでみました。

Hermes Desktop で「(プロジェクト名)のデータセット一覧を出して」と頼むと、lifelog と lifelog_ops の2件が返ってきた画面
Hermes Desktop で「(プロジェクト名)のデータセット一覧を出して」と頼むと、lifelog と lifelog_ops の2件が返ってきた画面

2つのデータセットが返ってきて、Claude Code から同じツールを呼んだ結果と一致しました。

ハマりポイント

罠①:Mac の hermes コマンドが動かない

Claude Code の MCP 設定を取り込むコマンド(hermes import-agent claude-code)を Mac のターミナルで打ったら、No such file or directory になりました。

原因は、~/.local/bin/hermes に以前のインストールで置かれたランチャーが残っていて、参照先がもう消えていたことでした。参照先は ~/.hermes の中のローカル版で、Docker で動かす構成を作ったときに、「Desktop アプリが勝手にローカル版をインストールした」件で消したものです(詳しくはこちらの記事)。Hermes 本体はコンテナの中にいるので、CLI は docker exec -it hermes hermes ... で呼ぶのが正解でした。

なお、import-agent をコンテナの中で動かしても、Mac 側の Claude Code の設定ファイルはコンテナから見えないので、この構成では使えないと判断しました(試してはいません)。

教訓:まず「Hermes がどこで動いているか」を確認する。stdio の MCP は、Hermes が動いている場所で起動される。

罠②:コンテナから接続すると 421 Misdirected Request

コンテナの中から http://host.docker.internal:8790/mcp に接続すると、421 が返ってきました。サーバーのログには Invalid Host header の警告と、"POST /mcp HTTP/1.1" 421 Misdirected Request が出ていました。

原因は、FastMCP の DNS リバインディング保護です。許可リストにない Host ヘッダー(host.docker.internal:8790)を拒否していました。許可リストに host.docker.internal を追加して、サーバーを再起動したら通りました。

教訓:421 は「届いたけど Host を拒否された」というサイン。ネットワークがつながっていないわけではない。

保護そのものを切るのではなく、許可するホストを1つ足すだけにしています。

罠③:hermes mcp add が最後に「Cancelled.」になる

接続に成功してツールが6件表示されたのに、最後に Cancelled. と出て設定が保存されませんでした。mcp test を打っても、設定にないと言われます。

✗ Server 'bigquery' not found in config.

原因は、docker exec に -it を付けていなかったことでした。対話式の質問に入力が渡らず、キャンセル扱いになっていました。このとき GetPassWarning: Can not control echo on the terminal. という警告も出ていました。

教訓:対話式の CLI をコンテナで動かすときは -it を付ける。

罠④:引数を付け忘れる

hermes mcp add だけを打つと、サーバー名が必要だと言われます。

hermes mcp add: error: the following arguments are required: name

hermes mcp add bigquery --url ... のように、名前と URL を指定します。

確かめた環境

  • iMac(M3)、Docker Desktop for Mac
  • Hermes Agent v0.21.5(Docker イメージ nousresearch/hermes-agent:v2026.9.24)、Desktop アプリは同じタグから自分でビルドしたもの
  • MCP サーバーは Python の MCP SDK(1.x)の FastMCP で作ったもの。2.x では mcp.server.fastmcp の import が通らなくなるので、こちらの記事のように 1.x に固定しています

使ってみて

Hermes から BigQuery を使ってみると、Claude Code から聞いて使うのとほぼ同じことができたかなと思います。

使い分けは、Claude Code は主に開発用、Hermes Agent は秘書のような使い方を考えています。運用している自作ツールの状況や、最近やったこと、ヘルスケアのデータなどを貯めているので、それを好きなときに取り出したり、それについて相談したり、次のネタを探したりするときに使おうと思っています。

Claude Code に直接聞いてもよいのですが、Claude Code はセッションごとに、そのリポジトリのフォルダまで移動して起動しています。ちょっと聞きたいだけのときに、毎回それをするのは面倒です。Hermes なら、BigQuery のデータは MCP 経由でそのまま取れます。こういう「自分の情報を聞く」使い方は、将来は Hermes に集めたいと思っています。

BigQuery 以外の場所に貯めている情報(AI とのセッションのログなど)もあるので、次はそれを取り出す MCP を作ってみようと思っています。作ったら、また書いてみようと思います。

まとめ

  • Hermes 本体が Docker の中にいるなら、Mac 側の MCP は stdio ではなく HTTP で公開する
  • コンテナからは host.docker.internal:<ポート> で届く。待ち受けは 127.0.0.1 のままでよかった
  • FastMCP の Host 許可リストに host.docker.internal を足さないと 421
  • コンテナの中の CLI を対話で使うときは docker exec -it

1つの MCP サーバーを Claude Code と Hermes の両方から使えるようになり、道具を増やしても、同じデータに同じ仕組みを通してアクセスできるようになりました。

あわせて読みたい

Docker で動かす Hermes Agent に、Mac 上の自作 BigQuery MCP サーバーをつなぐ:stdio が使えない理由と 421 の罠

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

プログラミング、ゲーム、ガジェットが好き。ブログを書くことは自分の役に立つのか?を検証中。まったり情報発信しながら、少しでも誰かの役に立てれば幸いです。

目次