Claude Media
MCP tunnelsに接続できないときのトラブルシューティング

MCP tunnelsに接続できないときのトラブルシューティング

MCP tunnelsの接続失敗を3層に分けて切り分ける手順。OAuth・証明書・IP検証・cloudflared接続それぞれの原因と対処法。

MCP tunnelsの接続失敗は3層構造で切り分ける

MCP tunnelsは、プライベートネットワーク内のMCPサーバーへインバウンドポートを開けずに到達させるトンネルです。ここを経由するリクエストは、cloudflaredからトンネルエッジへの外向き接続、AnthropicからプロキシへのTLS(内側TLS)、そしてプロキシからアップストリームのMCPサーバーへのルーティングとIP検証という3つの層を順に通ります。障害はこの3層のどこか1箇所で起きるので、上から順に切り分けるのが最短経路です。原因を1つ飛ばして推測から入ると、実際には外向き接続が張れていないだけなのに証明書やIP検証を疑って時間を浪費しがちです。一般的なMCPサーバーの接続不良は設定・起動・認証・ツール表示の4層で切り分けますが、トンネルはこの3層がさらに追加で挟まる点を押さえておくと、どちらの原因を疑うべきか迷いません。なおMCP tunnelsはResearch Preview段階の機能で、利用には申請が必要です。稼働率・サポート・継続性の保証は付かず、Anthropicはいつでも仕様変更や提供終了を行えるとしています。挙動が本記事と食い違うときは、まず提供状態の変化を疑ってください。

クイックリファレンス — 症状と対処

症状原因対処
エージェントの「+ MCP Server」候補にトンネルが出てこない原因候補に出るのはセッションと同じワークスペースにあり、有効なCA証明書を持つトンネルだけ対処CA証明書を登録するか、トンネルを作成したワークスペースでセッションを開く
呼び出し元にHTTP 500、cloudflaredのログにNo ingress rules were defined原因cloudflaredにローカルの転送先が設定されていない対処cloudflaredサービスに--url http://localhost:8080network_mode: "service:mcp-proxy"を追加する
プロキシログにno route for host原因tunnel_domainが割り当てられたドメインと一致していない、またはconfig.yaml編集後に再起動していない対処トンネル詳細ページに表示された正確なドメインを設定し、docker compose restart mcp-proxyで再起動する
プロキシログにIP validation failed: <ip> is not a private address原因アップストリームのMCPサーバーがRFC1918の範囲外に解決されている対処後述の「IP検証エラーを切り分ける」を参照
プロキシがcannot unmarshal !!seq into map[string]stringで終了する原因routesをYAMLのリストとして書いている対処routes: { name: http://host:port }の連想配列形式に直す
curl https://<proxy>:8080wrong version numberで失敗する原因想定どおりの挙動。リスナーは平文WebSocketで、TLSはWSストリームの内側で行われる対処Managed AgentかMessages API経由で疎通確認する

設定ファイル起因のエラーを深掘りする

クイックリファレンスに挙げた症状のうち、設定ファイルの書き方そのものが原因のものは、エラーメッセージから原因を一意に特定できます。

no route for hostがプロキシログに出る場合、原因は2つに絞れます。1つはtunnel_domainの値が、トンネル詳細ページに表示された実際のドメインと食い違っていること。もう1つはconfig.yamlを編集した後にプロキシを再起動していないことです。この設定は再読み込みされないため、値を直したら必ずdocker compose restart mcp-proxy(またはHelmなら該当Podの再作成)を挟みます。

cannot unmarshal !!seq into map[string]stringでプロキシが起動に失敗する場合は、routesをYAMLのリスト記法(- name: url)で書いてしまっています。routesはキーと値のペアからなる連想配列であり、routes: { docs: http://internal-wiki:8080 }のようなマップ記法が正しい書き方です。

open /data/tls.key: permission deniedは、鍵ファイルのパーミッションが0600のままで、プロキシコンテナが非rootユーザーで動いているために読めていない状態です。chmod 644 data/tls.keyで解消します。

No ingress rules were definedがcloudflaredのログに出て呼び出し元にHTTP 500が返る場合は、cloudflaredにローカルの転送先が設定されていません。cloudflaredサービスの起動オプションに--url http://localhost:8080を渡し、network_mode: "service:mcp-proxy"でプロキシと同じネットワーク名前空間に置きます。

トンネルの前提を疑う前にワークスペースと証明書を確認する

エラーメッセージが出る前の段階でつまずくケースもあります。エージェントの「+ MCP Server」ピッカーにトンネルが出てこないときは、そもそも一覧に載る条件を満たしていないだけのことが多く、設定ミスではありません。ピッカーに表示されるのは、そのセッションと同じワークスペースにあり、かつ有効なCA証明書を少なくとも1枚持つトンネルだけです。証明書をまだ登録していないか、トンネルを作成したのと別のワークスペースでセッションを開いていないかを先に確認します。

また、curl https://<proxy>:8080のようにプロキシへ直接HTTPSでアクセスしてwrong version numberが返るのは故障ではありません。リスナー自体は平文のWebSocketで待ち受けており、TLSはそのWebSocketストリームの内側で行われる設計だからです。疎通確認はManaged AgentセッションかMessages API経由で行います。

OAuthがソースIP許可リストの背後で失敗するときの対処

自組織の認可サーバーが送信元IPで許可リストを組んでいると、/token/register、ディスカバリーエンドポイントへAnthropicのバックエンドが到達できずOAuthが失敗します。Anthropicの送信元IP範囲を丸ごと許可リストに入れたくない場合は、バックエンド間のOAuth呼び出しだけをトンネル経由に切り出し、ブラウザ向けの/authorizeは既存の公開ホスト名に残す構成が使えます。

  1. 認可サーバー用のルートをプロキシに追加する
routes:
  mcp: http://your-mcp-server:8080
  auth: http://your-auth-server:8080

routes編集後はdocker compose restart mcp-proxy(またはHelmならhelm upgrade)でプロキシを再起動します。

  1. ディスカバリーメタデータをエンドポイント別に分ける。認可サーバーの/.well-known/oauth-authorization-serverで、authorization_endpointだけを既存の許可済みホストに向け、それ以外をトンネルに向けます。
{
  "issuer": "https://auth.<tunnel-domain>",
  "authorization_endpoint": "https://<your-allowlisted-host>/authorize",
  "token_endpoint": "https://auth.<tunnel-domain>/token",
  "registration_endpoint": "https://auth.<tunnel-domain>/register",
  "code_challenge_methods_supported": ["S256"]
}
  1. MCPサーバー側もトンネルのissuerを参照させる/.well-known/oauth-protected-resourceでトンネルのホスト名を認可サーバーとして指すよう設定します。

この構成なら、ユーザーのブラウザは許可済みの既存ホストへ/authorizeを叩き、Anthropicのバックエンドは/token/register・ディスカバリー文書をトンネル越しに取得できます。

setupコンポーネントの認証エラーを切り分ける

setupコンポーネントはOIDC JWTをフェデレーションルール経由で交換してTunnels APIに認証します。この交換が失敗するときに疑うべき箇所は、subject(トークンの主体がフェデレーションルールの想定と一致しているか)、audience(トークンの宛先がAPI側の期待値と一致しているか)、issuer(発行元のOIDCプロバイダーが正しく登録されているか)、JWKS(署名検証用の公開鍵セットを取得できているか)、有効期限(トークンが失効していないか)の5点で、これは一般的なWorkload Identity Federationの失敗パターンと同じ切り分け方です。MCP tunnels固有の原因は次の2つです。

  • Helmチャートの既定オーディエンスはapi.anthropic.com(スキームなし)です。フェデレーションルール側のオーディエンスがhttps://api.anthropic.comなら、api.wif.audienceをそちらに合わせます。
  • 交換自体は成功したのに403が返るなら、ルールのスコープにworkspace:manage_tunnelsが含まれていないか、ルールのサービスアカウントがそのトンネルのワークスペースに参加していません。スコープを付与するかワークスペースへ追加します。

Helm環境では、setupコンポーネントはpre-installフックのJobとして動きます。失敗するとJobは調査用に残るため、kubectl logs job/mcp-tunnel-setup -n mcp-tunnelでログを確認し、再試行の前に手動で削除します。

helm uninstall mcp-tunnel -n mcp-tunnel
kubectl -n mcp-tunnel delete job mcp-tunnel-setup

証明書エラーとIP検証エラーを切り分ける

内側TLSでAnthropicがプロキシの証明書を拒否すると、プロキシはtls handshake failedをログに出します。サーバー証明書の有効期限切れ、SANが*.<tunnel-domain>と一致していないこと、署名したCAがそのトンネルに登録されていないことの3点を確認します。

IP検証は既定でRFC1918のプライベート範囲(10.0.0.0/8172.16.0.0/12192.168.0.0/16)だけを許可し、プロキシからアップストリームへの接続はIPv4限定です。マネージドKubernetesの中にはService CIDRをRFC1918の外に割り当てるディストリビューションもあるため、まず自分のクラスタを確認します。

kubectl get svc kubernetes -n default -o jsonpath='{.spec.clusterIP}'

このアドレスがプライベート範囲の外にあり、かつ正当なものなら、最も狭いCIDRをupstream.allowed_ipsに追加します。allowed_ipsはRFC1918の既定値を拡張するのではなく置き換えるため、他のアップストリームMCPサーバーが使っている範囲も一緒に書いておく必要があります。

upstream:
  allowed_ips:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    - 127.0.0.0/8 # ローカルテスト用のループバック

トンネル自体が繋がらないときに見る場所

cloudflaredのログをまず確認します。多いのはTUNNEL_TOKENが未設定・期限切れ・コピーミスのいずれかと、ポート7844のTCP/UDP outboundをファイアウォールが塞いでいるケースです。社内プロキシ経由でしか外部通信できない環境では、7844番ポートだけが素通しになっていないことも見落としがちなので、ネットワーク要件の宛先(トンネルエッジのIP範囲)と合わせて確認します。cloudflaredがUDP受信バッファサイズについて警告を出すことがありますが、これはQUICのチューニングに関するヒントであってエラーではありません。

MCP tunnelsのトラブルシュートは何を教えてくれるか

一覧にした症状のうち、IP validation failedwrong version numberは一見エラーですが、実際はSSRF対策とインナーTLSの設計がそのとおりに動いている証拠です。バグを疑って設定を緩める前に、その挙動が仕様どおりかを確認する価値があります。cloudflaredのUDP受信バッファサイズ警告も同じ性質で、QUICのチューニングに関する情報であって障害ではありません。「エラーらしきログが出ている = 何かが壊れている」という前提そのものを、トンネル運用では一度疑ってかかる必要があります。

トンネル特有の障害は、一般的なMCPサーバーの接続トラブル(設定ファイルの記述ミスや認証トークンの入れ違いが中心)とは層が異なり、ネットワークとTLSの経路そのものを疑う必要がある点が最大の違いです。切り分けの起点も違います。一般的なMCP接続トラブルはクライアント側の設定ファイルから疑うのが定石ですが、トンネルの場合はまずcloudflaredのログ、次にプロキシのログ、最後にアップストリームのMCPサーバー自体、という順で外側から内側へたどるほうが早く原因にたどり着けます。

まとめ

障害の切り分けは、外向き接続 → 内側TLS → ルーティングとIP検証、の3層の順で進めます。OAuthの許可リストが原因ならエンドポイント分割、setupコンポーネントの認証エラーならWIFの失敗パターンとHelmチャートのオーディエンス設定、証明書エラーならSANと登録CAの一致、IP検証エラーならallowed_ipsの置き換えという、それぞれの層に固有の性質を思い出し、該当する節から順に確認してください。

この記事を共有:XはてブLinkedIn
MCP をもっと見る →