Claude Media
MCP tunnelsをHelmでデプロイする手順 — 通信制限とOIDCトークン供給

MCP tunnelsをHelmでデプロイする手順 — 通信制限とOIDCトークン供給

HelmチャートでMCP tunnelsをKubernetesにデプロイし、NetworkPolicyでの通信制限とOIDCトークンの差し替えまで、チャート2.0.2の設定キー単位でまとめます。

HelmチャートはKubernetes上に何を作るのか

Anthropicが配布するHelmチャートは、MCP tunnelsのトンネルスタック(cloudflaredとプロキシの2コンテナ)を単一のDeploymentとしてKubernetesクラスタにインストールします。接続先は、チャートのsetupフックが作る新規トンネルか、Consoleで先に作った既存トンネルのどちらかです。作成手順はMCP tunnelsをConsoleで作成・管理する手順で扱っています。

チャートのバージョンは2.0.2です。バージョンは変わり得るので、実行前にhelm show valuesで最新のデフォルト値を取得してから進めます。

観点Helm(本記事)Docker Compose
想定環境Helm(本記事)既にKubernetesクラスタを運用中Docker Compose単一ホスト・小規模構成
証明書更新Helm(本記事)CronJobで自動化可能Docker Compose手動での定期更新
スケールHelm(本記事)Deploymentのレプリカ数で調整Docker Composeホスト単位でしか増やせない
導入の手数Helm(本記事)values.yaml設計とfederation rule設定が必要Docker Compose少ない

単一ホストでの検証や小規模構成なら、MCP tunnelsをDocker Composeでデプロイする手順のほうが手数は少なくて済みます。

事前に必要なもの

  • トンネル。プログラム的アクセスではチャートのsetupフックが未指定なら新規作成し、指定すればConsoleで作った既存トンネルに接続します。手動フローでは必ずConsoleで先に作成し、トンネルトークンとドメインを控えます
  • Tunnels APIへの認証手段。プログラム的アクセスならworkspace:manage_tunnelsスコープのfederation rule、手動ならトンネルトークンとCA証明書
  • helmkubectlが使えるKubernetesクラスタ。手動フローではopenssl(1.1.1以降)も使います
  • アウトバウンド通信api.anthropic.comへの443/TCPと、トンネルエッジへの7844/TCP・UDP
  • 到達可能なMCPサーバーが1つ以上。無ければFastMCPのサンプルDeploymentで代用できます
  • Zero Data RetentionやHIPAA BAAの対象範囲の確認。研究プレビュー段階の機能は対象可否が個別に決まるため、規制対象データを扱う前に自組織の要件と突き合わせます

手元にMCPサーバーが無いときのテスト用サンプル

到達可能なMCPサーバーをまだ持っていない場合、helloツールが1つあるだけのFastMCPサンプルをDeploymentとして立てられます。

kubectl create namespace mcp-tunnel --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mcp-tunnel apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: { name: hello-mcp }
spec:
  replicas: 1
  selector: { matchLabels: { app: hello-mcp } }
  template:
    metadata: { labels: { app: hello-mcp } }
    spec:
      containers:
        - name: hello-mcp
          image: python:3.13-slim
          command: ["sh", "-c", "pip install --quiet mcp && python /app/hello_server.py"]
---
apiVersion: v1
kind: Service
metadata: { name: hello-mcp }
spec:
  selector: { app: hello-mcp }
  ports: [{ port: 9000, targetPort: 9000 }]
EOF

このサンプルを使う場合、gateway.config.routesecho: http://hello-mcp:9000にします。以降の検証ではこのechoルートを使います。

プログラム的アクセスでインストールする

まずクラスタのOIDC発行者を登録し、federation ruleを作ります。setupコンポーネントはリリース名から導出される専用のServiceAccountで動くため、リリース名がmcp-tunnel以外ならhelm templateでServiceAccount名を確認してから進めます。

項目
Subjectsystem:serviceaccount:mcp-tunnel:mcp-tunnel-setup
Audienceapi.anthropic.com(スキームなし)
Scopeworkspace:manage_tunnels

デフォルト値を取得し、federation ruleのIDとルート先を設定します。

helm show values \
  oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
  --version 2.0.2 > values.yaml
values.yaml(プログラム的アクセスで編集する主要キー)
api:
  wif:
    federationRuleId: "fdrl_..."
    organizationId: "00000000-0000-0000-0000-000000000000"
    # workspaceId: "wrkspc_..."   # 非デフォルトワークスペースのときだけ設定
 
tunnel:
  id: ""              # 空なら新規作成。既存トンネルに繋ぐならtnl_...を指定
  tokenVersion: "1"   # ローテーション時にこの値を上げる
 
gateway:
  config:
    routes:
      docs: http://docs-mcp.internal:8080
      search: http://search-mcp.internal:8080

レンダリング結果を確認してからインストールします。

helm template mcp-tunnel \
  oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
  --version 2.0.2 -n mcp-tunnel -f values.yaml > rendered.yaml
 
helm install mcp-tunnel \
  oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
  --version 2.0.2 \
  --namespace mcp-tunnel --create-namespace \
  -f values.yaml

setupコンポーネントはHelmのpre-installフックJobとして動くため、helm installはこのJobが完了するまでブロックします。成功するとHelmがJobを自動で削除します。tunnel.idを空にした場合、作成されたトンネルのIDとドメインはmcp-tunnelという名前のSecretに保存され、次のコマンドで読み出せます。

kubectl -n mcp-tunnel get secret mcp-tunnel \
  -o jsonpath='{.data.tunnel-domain}' | base64 -d

api.wif.*の値は識別子であって秘密情報ではないため、Helmのリリース履歴Secretに残ってもリスクにはなりません。実際に機微なのは、setupコンポーネントが作るmcp-tunnel Secret(トンネルトークンとTLS秘密鍵を保持)のほうです。

手動フローでインストールする

setup.enabled: falseにすると、チャートはAPI呼び出しを一切行わず、setupコンポーネントも証明書更新のCronJobも動きません。Workload Identity Federationを組みたくない場合はこちらを使います。

ConsoleでトンネルとCA証明書を用意した後、チャートが読む2つのSecretを作ります。Secret名は変更できますが、中のキー名は固定です。

kubectl create namespace mcp-tunnel --dry-run=client -o yaml | kubectl apply -f -
kubectl -n mcp-tunnel create secret generic mcp-tunnel-token \
  --from-literal=tunnel-token='eyJ...'
kubectl -n mcp-tunnel create secret generic mcp-tunnel-cert \
  --from-file=tls.crt=data/tls.crt --from-file=tls.key=data/tls.key

values.yamlでは以下を設定します。

  • setup.enabled: false
  • external.tunnelTokenSecretName
  • external.serverCertSecretName
  • gateway.config.tunnel_domain(setup有効時はSecretから自動注入されるが、無効時は明示が必須)

この先のhelm templatehelm installはプログラム的アクセスと同じコマンドです。

NetworkPolicyで外向き通信(egress)を絞る(任意設定)

プロキシPodへの受信通信は既定で遮断されます(networkPolicy.ingress.enabledの既定値がtrue)。さらにPodからの送信も絞るなら、networkPolicy.egress.enabled: trueにし、networkPolicy.egress.mcpServersにPodラベルセレクタかCIDRを列挙して、アップストリームのMCPサーバー以外への通信を遮断します。cloudflaredからトンネルエッジへの送信は別枠のnetworkPolicy.egress.cloudflaredEgressCIDRsで許可されるので、この2つは独立して設定します。

NetworkPolicyはネットワーク経路を絞るだけで、トンネル自体はアップストリームのMCPサーバーを認証しません。各MCPサーバー側にOAuthかbearer認証を個別に設定する必要があり、仕組みはリモートMCPのOAuth認証で解説しています。

自前のOIDCトークンを供給する(任意設定)

チャートは既定でKubernetesのServiceAccountトークンをsetupコンポーネントに投影します。SPIFFEやVault、クラウドSDKのサイドカーなど別のIDプロバイダのトークンを使いたい場合は、setup.extraVolumessetup.extraVolumeMountsでトークンファイルをマウントし、api.wif.tokenFileにそのマウントパスを指定します。チャートはANTHROPIC_IDENTITY_TOKEN_FILE環境変数をそのパスに設定し、setupコンポーネントはそこからトークンを読みます。

トークンをローテーションする

プログラム的アクセスでは、values.yamltunnel.tokenVersionを上げてから--set setup.force=trueを付けてアップグレードします。setupコンポーネントは、フォースしない限りアップグレード時には再実行されません。

helm upgrade mcp-tunnel \
  oci://us-docker.pkg.dev/anthropic-public-registry/charts/mcp-tunnel \
  --version 2.0.2 -n mcp-tunnel \
  -f values.yaml --set setup.force=true

手動フローでは、ConsoleでRotate tokenを押した後、mcp-tunnel-token Secretを更新してDeploymentをロールアウトし直します。

証明書を更新する

プログラム的アクセスでは、リリース名にちなんで命名されたCronJob(サフィックス-cert-renew)が既定で毎日UTC 0時にsetup renew-certを実行します。有効期限までserverCert.renewBefore(既定30日)を切っていない限りno-opです。更新はローカルで完結し、Secretに保存済みのCAで新しい証明書に署名するだけなのでAPI呼び出しは発生せず、プロキシはSecretのマウントを自動でホットリロードします。

手動フローにはCronJobが無いため、既存のCAで証明書を作り直し、Secretを更新する作業を利用者側で定期的に行います。

チャート1.xからアップグレードする

チャート2.0.0で、トンネルIDの置き場所がapi.wif.tunnelIdからtunnel.idに変わりました。アップグレード前にvalues.yamlを編集し、tnl_...の値をtunnel.idへ移してapi.wif.tunnelIdを削除します。federation ruleのスコープも、Console側でorg:manage_tunnelsからworkspace:manage_tunnelsへ更新が必要です。

よくあるつまずき

  • AudienceのURIをそのままコピーする。Console画面が提案するhttps://api.anthropic.comをfederation ruleにそのまま設定すると、チャートの既定値api.anthropic.comとスキームの有無が一致せず認証が失敗します
  • --reuse-valuesに頼る。Helmのdeep-merge挙動は、削除したはずのルートを暗黙に残すことがあります。ルート変更時はvalues.yaml全体を管理し、--reuse-valuesは使いません
  • setup.force=trueを付け忘れてトークンをローテーションするtokenVersionを上げただけではsetupコンポーネントは再実行されません
  • Service CIDRが標準外のマネージドKubernetesでroutesが届かない。クラスタ内Serviceを指す場合はgateway.config.upstream.allowed_ipsに自クラスタのCIDRを追加します
  • NetworkPolicyの送信許可を1本にまとめる。MCPサーバーへの許可(networkPolicy.egress.mcpServers)とトンネルエッジへの許可(cloudflaredEgressCIDRs)は別々の設定項目です

まとめ

Helmでのデプロイは、Workload Identity FederationのAudience一致とNetworkPolicyの2軸を最初に押さえれば、あとはDocker Composeと同じ考え方で運用できます。トークンローテーションはプログラム的アクセスならtokenVersionとforceアップグレード、証明書更新は既定で日次CronJobが肩代わりするため、手動フローに比べて運用の手離れがよい設計です。

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