コンテンツにスキップ
これは最新の機能を備えた最新リリースですが、長期サポートは提供されません。サポートを継続するには、新しいリリースごとにアップグレードするか、四半期ごとの安定版リリースをご利用ください。

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

agentgateway で MCP サーバーを公開して保護する

Page as Markdown

agentgateway バーチャルランタイムを通じて複数のリモート MCP サーバーを公開し、ユーザー、グループ、ツールごとにアクセスを制限する方法を学びます。

中央プロキシがない場合、MCP サーバーはインフラストラクチャ全体に分散し、共有認証、一貫した検出エンドポイント、どのユーザーがどのサーバーに到達でき、特定のツールを呼び出せるかを強制する方法がありません。サーバー URL に到達できる MCP クライアントは、そのすべてのツールへ完全にアクセスできます。Solo Enterprise for agentregistry は、MCP サーバーの前段に配置される単一のポリシー適用プロキシである agentgateway バーチャルランタイムでこの問題を解決し、AI アーティファクトカタログの他の部分と同じ方法でアクセスを管理できるようにします。

agentgateway は、MCP トラフィック用のロードバランサーと考えることができます。ゲートウェイ管理者がデリゲーションルールを一度付与すると、レジストリユーザーはゲートウェイ構成に触れずに、その背後の MCP サーバーを追加または削除できます。各サーバーには、プロキシ経由の安定したルーティング可能な URL が割り当てられます。アクセスポリシーは、どのユーザーグループが何を表示でき、デプロイ済みエージェントが何を呼び出せるかを制御します。

Tip

Solo Enterprise for agentregistry を初めて使用する場合は、開始する前に主要な概念とアーキテクチャを確認してください。

このガイドについて

このガイドでは、3 つの MCP サーバーをカタログに登録し、agentgateway プロキシ経由で公開してから、JWT 認証、グループレベルのサーバー制限、ユーザー単位のツールフィルタリング、レートリミットなどのセキュリティポリシーを重ねて適用します。

このガイドを完了すると、次のセキュリティレイヤーが適用された MCP ゲートウェイが動作します。

  • 安定したルーティング可能な URL を持つ中央 agentgateway プロキシ経由で公開される 3 つの MCP サーバー。
  • すべての MCP トラフィックに必須の JWT 認証。認証されていないリクエストはプロキシで拒否されます。
  • developers グループのユーザーが特定の MCP サーバー上のツールを呼び出せないようにする、グループレベルのサーバー制限。
  • 個々のユーザーを特定のツールサブセットに制限する、ユーザー単位のツールフィルタリング。
  • 不正利用を防ぐためのリクエストレートリミット。

始める前に

このガイドを完了するには、OIDC プロバイダーとして Keycloak を使用するように構成された Solo Enterprise for agentregistry の実行中のインストール、クラスターにインストールされた Solo Enterprise for agentgateway、agentgateway をレジストリに接続するバーチャルランタイム、および arctl CLI が必要です。これらのコンポーネントをまだセットアップしていない場合は、まず次の手順を完了してください。

  1. OIDC プロバイダーとして Keycloak をセットアップします。必要な realm 設定、クライアント、シークレットを含めて Keycloak をインストールするために、必ず General setup タブの手順に従ってください。

  2. Solo Enterprise for agentregistry をインストールし、arctl CLI をインストールします。

  3. 次のコマンドを実行して、ログイン済みであることを確認します。

    arctl user whoami
  4. まだ実行していない場合は、Solo Enterprise for agentregistry サーバーをポート 12121 でポートフォワードします。

    kubectl -n agentregistry-system port-forward svc/agentregistry-enterprise-server 12121:12121
  5. Solo Enterprise for agentgateway をインストールし、バーチャルランタイムとして接続します。


ステップ 1: リモート MCP サーバーをカタログに追加する

ゲートウェイ経由で公開するリモート MCP サーバーを登録します。このステップでは、次の MCP サーバーのカタログエントリを追加します。

  • Solo.io docs: Solo.io 製品ドキュメントにアクセスできる MCP サーバー。
  • GitHub Copilot MCP: GitHub にアクセスできる MCP サーバー。このサーバーでは、認証に GitHub personal access token が必要です。
  • Self-hosted sample server: example_sum ツールと example_echo ツールを備えた、クラスター内の Python サーバー。

Solo.io docs MCP サーバーをカタログに登録します。

arctl apply -f- <<EOF
apiVersion: ar.dev/v1alpha1
kind: MCPServer
metadata:
  name: solo-docs
spec:
  title: Solo.io docs MCP
  description: "MCP server for Solo.io product documentation."
  remote:
    type: streamable-http
    url: https://search.solo.io/mcp
EOF

出力例:

✓ MCPServer/solo-docs (latest) created

認証ヘッダーを必要とする公開 MCP サーバーのカタログエントリを作成します。この例では、すべてのリクエストに GitHub personal access token が必要な GitHub Copilot MCP サーバーを使用します。

  1. GitHub で personal access token を作成し、環境変数に保存します。詳細については、GitHub docsを参照してください。

    arctl user whoami
  2. GitHub Copilot MCP サーバーをカタログに登録します。

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: MCPServer
    metadata:
      name: githubcopilot-mcp
    spec:
      title: GitHub Copilot MCP
      description: "GitHub Copilot remote MCP server"
      remote:
        type: streamable-http
        url: https://api.githubcopilot.com/mcp
        headers:
        - name: Authorization
          value: "Bearer ${GH_PAT}"
    EOF

    出力例:

    ✓ MCPServer/githubcopilot-mcp (latest) created
    

arctl CLI を使用して Python MCP サーバーをスキャフォールドし、ビルドしてクラスターにデプロイしてから、カタログに登録します。

  1. MCP サーバーのスキャフォールドを作成します。次のコマンドは、example_echo ツールと example_sum ツールを含む FastMCP Python サーバーを持つ mymcp ディレクトリを作成します。

    arctl init mcp mymcp --framework fastmcp --language python \
      --description "Sample MCP server" \
      --image localhost:5001/mymcp:latest

    出力例:

    ✓ Created MCP server: mymcp (framework: fastmcp, language: python)
    
  2. MCP サーバーのコンテナイメージをビルドします。

    arctl build mymcp

    出力例:

    Building MCP server image: localhost:5001/mymcp:latest
    ✓ Successfully built Docker image: localhost:5001/mymcp:latest
    
  3. Kind クラスターを使用している場合は、イメージをクラスターにロードします。それ以外の場合は、ビルドしたイメージにクラスターからアクセスできることを確認します。

    kubectl -n agentregistry-system port-forward svc/agentregistry-enterprise-server 12121:12121
  4. MCP サーバーをクラスターにデプロイします。

    kubectl create namespace mcp-test
    kubectl apply -n mcp-test -f - <<EOF
    apiVersion: v1
    kind: Service
    metadata:
      name: mymcp
      labels:
        app: mymcp
    spec:
      selector:
        app: mymcp
      ports:
        - name: http
          port: 3000
          targetPort: 3000
      type: ClusterIP
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mymcp
      labels:
        app: mymcp
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mymcp
      template:
        metadata:
          labels:
            app: mymcp
        spec:
          containers:
            - name: mymcp
              image: localhost:5001/mymcp:latest
              imagePullPolicy: Never
              args: ["--transport", "http", "--host", "0.0.0.0", "--port", "3000"]
              ports:
                - containerPort: 3000
              readinessProbe:
                tcpSocket:
                  port: 3000
                initialDelaySeconds: 5
                periodSeconds: 10
    EOF
  5. MCP サーバーの Pod が実行中であることを確認します。

    kubectl get pods -n mcp-test

    出力例:

    NAME                     READY   STATUS    RESTARTS   AGE
    mymcp-6f7d9b8c4d-xvpzk   1/1     Running   0          30s
    
  6. クラスター内サーバーをカタログに登録します。

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: MCPServer
    metadata:
      name: mymcp-remote
    spec:
      title: My MCP Server
      description: "In-cluster MCP server exposed via virtual gateway"
      remote:
        type: streamable-http
        url: http://mymcp.mcp-test.svc.cluster.local:3000/mcp
    EOF

    出力例:

    ✓ MCPServer/mymcp-remote (latest) created
    

ステップ 2: MCP サーバーをゲートウェイで公開する

  1. 前のステップで登録した各 MCP サーバーに対して Deployment リソースを作成します。各 Deployment は、runtimeConfig.route.pathSuffix に指定したパスで、ゲートウェイ経由でサーバーを公開します。到達可能な完全パスは /registry<pathSuffix> になります。

    arctl apply -f- <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: solo-docs
    spec:
      targetRef:
        kind: MCPServer
        name: solo-docs
        tag: latest
      runtimeRef:
        name: mcp-gateway
        kind: Runtime
      runtimeConfig:
        route:
          pathSuffix: /solo-docs
    EOF

    出力例:

    ✓ Deployment/solo-docs created
    
    arctl apply -f- <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: githubcopilot-mcp
    spec:
      targetRef:
        kind: MCPServer
        name: githubcopilot-mcp
        tag: latest
      runtimeRef:
        name: mcp-gateway
        kind: Runtime
      runtimeConfig:
        route:
          pathSuffix: /githubcopilot
    EOF

    出力例:

    ✓ Deployment/githubcopilot-mcp created
    
    arctl apply -f- <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: mymcp-remote
    spec:
      targetRef:
        kind: MCPServer
        name: mymcp-remote
        tag: latest
      runtimeRef:
        name: mcp-gateway
        kind: Runtime
      runtimeConfig:
        route:
          pathSuffix: /mymcp
    EOF

    出力例:

    ✓ Deployment/mymcp-remote created
    
    フィールド説明
    targetRefデプロイする MCPServer とタグです。
    runtimeRefゲートウェイリソースにリンクする Virtual ランタイムです。
    runtimeConfig.route.pathSuffix親 HTTPRoute プレフィックスに追加されるパスです。/ で始める必要があり、ランタイムごとに一意である必要があります。
  2. すべてのデプロイが準備完了であることを確認します。status.details.agentgateway.exposedAt フィールドには、MCP サーバーが公開されているゲートウェイ URL が含まれます。次のステップでは、これらの URL を使用して MCP サーバーアクセスをテストします。

    arctl get deployments -o yaml

    Solo.io docs MCP サーバーの出力例:

    ...
    status:
      conditions:
      - lastTransitionTime: "2026-08-10T16:56:22.918848756Z"
        message: deployed via Agentgateway at 1 URL(s) across 1 binding(s)
        reason: DeployedViaAgentgateway
        status: "True"
        type: Ready
      details:
        agentgateway:
          exposedAt:
          - listener: http
            url: http://172.18.0.13/registry/solo-docs
          lastReconciledAt: "2026-08-10T16:56:22Z"
        deploymentController:
          lastAppliedFingerprint: sha256:db2021728c1ebece9e537e1bf223a174f1cecec346db444a39b0cb4577415bea
    ...
    

    conditions[type=Ready].status が False の場合は、reason フィールドで原因を確認します。

    理由原因
    NoGatewayBoundagentregistry.solo.io/runtime: mcp-gateway ラベルを持つ Gateway または親 HTTPRoute がクラスター内で見つかりません。バーチャルランタイムをセットアップしたときに作成した親 HTTPRoute に、このラベルを追加したことを確認してください。
    NoAcceptedListenerラベル付き Gateway は存在しますが、Accepted=True かつ Programmed=True のリスナーがありません。詳細は kubectl describe gateway agentgateway-proxy -n agentgateway-system を実行してください。
    MCPServerNotRemote対象の MCPServer に spec.remote フィールドが設定されていません。Virtual ランタイムはリモート MCPServer のみをサポートします。
    InvalidRuntimeConfigruntimeConfig.route.pathSuffix がないか、/ で始まっていません。
  3. agentgateway コントローラーが対応するバックエンドリソースを作成したことを確認します。レジストリのリコンサイラーは、MCP サーバーごとに 2 種類の EnterpriseAgentgatewayBackend リソースを自動的に作成します。

    kubectl get enterpriseagentgatewaybackends -A

    出力例:

    NAMESPACE              NAME                                                 ACCEPTED   AGE
    agentregistry-system   gw-be-mcpserver-default-githubcopilot-mcp-ad756486   True       2m
    agentregistry-system   gw-be-mcpserver-default-mymcp-remote-2f3b6f39        True       2m
    agentregistry-system   gw-be-mcpserver-default-solo-docs-85fa95de           True       2m
    agentregistry-system   gw-dep-default-githubcopilot-mcp-b3c7dfdf            True       2m
    agentregistry-system   gw-dep-default-mymcp-remote-2978e6d3                 True       2m
    agentregistry-system   gw-dep-default-solo-docs-827635b0                    True       2m
    
    • gw-be-mcpserver-*: MCPServer カタログエントリごとに 1 つ作成されます。そのサーバーのアップストリームバックエンド構成を表します。
    • gw-dep-*: カタログ Deployment ごとに 1 つ作成されます。デプロイされた各インスタンスのルーティングエントリを構成します。

ステップ 3: MCP サーバーアクセスを確認する

MCP Inspector を使用して、ゲートウェイが各リモート MCP サーバーにトラフィックをルーティングすることを確認します。

  1. MCP Inspector ツールを開きます。

    npx modelcontextprotocol/inspector#0.21.2
  2. トランスポートタイプとして Streamable HTTP を選択し、status.details.agentgateway.exposedAt フィールドの MCP サーバー用ゲートウェイ URL を入力して、Connect をクリックします。

    1. http://172.18.0.4/registry/solo-docs など、Solo.io docs サーバーの URL を入力します。

    2. Tools タブに移動し、List Tools をクリックします。search、get_chunks、get_full_page ツールが表示されることを確認します。

    3. ツールを選択して試します。

    1. http://172.18.0.13/registry/githubcopilot など、GitHub Copilot サーバーの URL を入力します。

    2. Tools タブに移動し、List Tools をクリックします。GitHub ツールが表示されることを確認します。

    3. get_me ツールを選択して Run Tool をクリックします。GitHub ユーザー情報が表示されることを確認します。

    1. http://172.18.0.13/registry/mymcp など、クラスター内サーバーの URL を入力します。

    2. Tools タブに移動し、List Tools をクリックします。example_echo ツールと example_sum ツールが表示されることを確認します。

    3. example_echo ツールを選択し、メッセージ文字列を入力して Run Tool をクリックします。サーバーがそのメッセージをエコーバックすることを確認します。

ステップ 4: ゲートウェイで JWT 認証を必須にする

ゲートウェイがリモート MCP サーバーへリクエストをルーティングする前に、すべての MCP クライアントが有効な JWT を提示することを必須にします。

このステップで作成する EnterpriseAgentgatewayPolicy リソースは、各トークンの署名を IdP の公開鍵で検証し、リクエストを転送する前に iss と aud クレームを確認するように agentgateway プロキシを構成します。有効なトークンなしで到着したリクエストは、プロキシで 401 Unauthorized HTTP レスポンスとして拒否されます。このポリシーは親 HTTPRoute を対象とするため、ゲートウェイで公開されるすべての MCP サーバーに一律に適用されます。サーバーごとの構成は不要です。agentgateway が JWT 認証を処理する方法の詳細については、agentgateway JWT auth docsを参照してください。

  1. 発行者 URL から Keycloak JWKS パスを導出し、環境変数に保存します。このパスは $KEYCLOAK_ISSUER の realm 名と正確に一致する必要があります。

    export KEYCLOAK_JWKS_PATH="$(echo $KEYCLOAK_ISSUER | sed 's|.*\(/realms/[^/]*\).*|\1|')/protocol/openid-connect/certs"
    export KEYCLOAK_PORT=$(echo $KEYCLOAK_ISSUER | sed 's|.*:\([0-9]*\)/.*|\1|')
    echo $KEYCLOAK_JWKS_PATH
    echo $KEYCLOAK_PORT

    出力例:

    /realms/agentregistry/protocol/openid-connect/certs
    8080
    
  2. リモート MCP ゲートウェイの親 HTTPRoute に JWT 認証ポリシーを適用します。このポリシーは、Keycloak の公開鍵セットを使用してトークンの署名を検証し、iss と aud クレームを確認します。

    kubectl apply -f - <<EOF
    apiVersion: enterpriseagentgateway.solo.io/v1alpha1
    kind: EnterpriseAgentgatewayPolicy
    metadata:
      name: mcp-gateway-jwt-auth
      namespace: agentgateway-system
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: agentregistry-delegate
      traffic:
        jwtAuthentication:
          mode: Strict
          providers:
          - issuer: "${KEYCLOAK_ISSUER}"
            audiences:
            - ar-backend
            jwks:
              remote:
                jwksPath: "${KEYCLOAK_JWKS_PATH}"
                backendRef:
                  name: keycloak
                  namespace: keycloak
                  port: ${KEYCLOAK_PORT}
    EOF
    フィールド説明
    targetRefsポリシーを親 HTTPRoute にアタッチします。JWT チェックは、ゲートウェイ経由でルーティングされるすべての MCP サーバーに適用されます。
    traffic.jwtAuthentication.modeStrict は、有効な JWT がないリクエストを 401 Unauthorized HTTP レスポンスで拒否します。トークンが存在する場合にクレームを抽出しつつ、認証されていないリクエストも許可するには Optional を使用します。
    traffic.jwtAuthentication.providers[].issuerJWT の iss クレームと一致する必要があります。Keycloak では、これは realm URL です。
    traffic.jwtAuthentication.providers[].audiencesトークンに要求する aud クレーム値です。レジストリは、発行するすべてのトークンのオーディエンスとして ar-backend を使用します。
    traffic.jwtAuthentication.providers[].jwks.remote.backendRefJWKS エンドポイントをホストする Kubernetes Service です。Keycloak インストールに合わせて name、namespace、port を調整してください。
  3. ポリシーが agentgateway コントローラーに受け入れられたことを確認します。

    kubectl get enterpriseagentgatewaypolicies -A

    出力例:

    NAMESPACE             NAME                  ACCEPTED   ATTACHED   AGE
    agentgateway-system   mcp-gateway-jwt-auth  True       True       30s
    

    ポリシーで Accepted: False が表示される場合は、kubectl describe enterpriseagentgatewaypolicy <name> -n agentgateway-system を実行して、status.ancestors[].conditions フィールドの拒否理由を確認します。

  4. Gateway リソースのステータスからゲートウェイ IP アドレスをエクスポートします。

    export GATEWAY_IP=$(kubectl get gateway agentgateway-proxy -n agentgateway-system \
      -o jsonpath='{.status.addresses[0].value}')
    echo $GATEWAY_IP
  5. ゲートウェイが認証されていないリクエストを拒否することを確認します。Authorization ヘッダーなしで、ゲートウェイプロキシ上で公開した MCP サーバーにリクエストを送信し、ゲートウェイが 401 Unauthorized HTTP レスポンスを返すことを確認します。

    curl -vik "http://${GATEWAY_IP}/registry/mymcp"
    curl -vik "http://${GATEWAY_IP}/registry/solo-docs"
    curl -vik "http://${GATEWAY_IP}/registry/githubcopilot"

    出力例:

    HTTP/1.1 401 Unauthorized
    content-length: 0
    authentication failure: no bearer token found
    
  6. 有効な JWT を使用して MCP initialize リクエストを送信し、ゲートウェイがリクエストを MCP サーバーにルーティングすることを確認します。

    arctl user login
    export TOKEN=$(arctl user info --show-tokens | jq -r '.access_token')
    curl -vik \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.1"}}}' \
      "http://${GATEWAY_IP}/registry/mymcp"
    curl -vik \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.1"}}}' \
      "http://${GATEWAY_IP}/registry/solo-docs"
    curl -vik \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.1"}}}' \
      "http://${GATEWAY_IP}/registry/githubcopilot"

    出力例:

    HTTP/1.1 200 OK
    content-type: text/event-stream
    ...
    event: message
    data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"mymcp","version":"0.1.0"}}}
    

    Note

    GitHub Copilot リクエストは、MCP サーバーをレジストリカタログに登録したときに有効な GH_PAT を指定している場合にのみ成功します。agentgateway プロキシは、検証後に受信した Keycloak JWT を削除し、リクエストを GitHub に転送する前に、Authorization ヘッダーを MCPServer 仕様の Bearer ${GH_PAT} 値に置き換えます。

ステップ 5: Keycloak グループとユーザーを作成する

次の数ステップでは、ユーザーグループおよび特定のユーザーに対して、特定の MCP サーバーとツールへのアクセスを制限する方法を確認します。これらの手順をテストするには、まず管理者以外のユーザーグループとユーザーを作成する必要があります。

このステップでは、Keycloak に developers グループと dev-user アカウントを作成します。

Keycloak Admin REST API を使用して、developers グループと、そのグループに所属するユーザーを作成します。

  1. Keycloak Admin REST API の管理者トークンを取得します。

    export KEYCLOAK_ADMIN_TOKEN=$(curl -s \
      -d "client_id=admin-cli" -d "username=admin" -d "password=admin" \
      -d "grant_type=password" \
      "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
      | jq -r .access_token)
  2. developers グループを作成します。

    curl -s -X POST \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      -H "Content-Type: application/json" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/groups" \
      -d '{"name": "developers"}'
  3. dev-user アカウントを作成し、developers グループに割り当てます。

    curl -s -X POST \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      -H "Content-Type: application/json" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users" \
      -d '{
        "username": "dev-user",
        "email": "dev-user@example.com",
        "firstName": "Dev",
        "lastName": "User",
        "enabled": true,
        "credentials": [{"type": "password", "value": "password", "temporary": false}]
      }'
    
    export DEV_USER_ID=$(curl -s \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=dev-user" \
      | jq -r '.[0].id')
    
    export DEVELOPERS_GROUP_ID=$(curl -s \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/groups?search=developers" \
      | jq -r '.[0].id')
    
    curl -s -X PUT \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users/${DEV_USER_ID}/groups/${DEVELOPERS_GROUP_ID}"
  4. developers グループと dev-user が作成され、グループ割り当てが設定されていることを確認します。

    curl -s \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/groups" \
      | jq '.[].name'
    curl -s \
      -H "Authorization: Bearer ${KEYCLOAK_ADMIN_TOKEN}" \
      "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/users?username=dev-user" \
      | jq '.[0].username'

    出力例:

    "developers"
    "dev-user"
    
  5. developers グループのユーザーに、カタログ内の MCP サーバーとデプロイへの完全なアクセス権を付与する AccessPolicy を作成します。

    cat <<EOF | arctl apply -f -
    apiVersion: ar.dev/v1alpha1
    kind: AccessPolicy
    metadata:
      name: developers
    spec:
      principals:
        - kind: Role
          name: developers
      rules:
        - actions:
            - "registry:*"
          resources:
            - kind: "server"
              name: "*"
            - kind: "agent"
              name: "*"        
    EOF

ステップ 6: ユーザーグループごとに MCP サーバーアクセスを制限する

デフォルトでは、認証されたユーザーはゲートウェイ経由ですべての MCP サーバーにリクエストを送信できます。特定の MCP サーバーとツールへのアクセスを制限するには、backend.mcp.authorization CEL 式を Deployment リソースに直接追加できます。agentregistry リコンサイラーは、インラインポリシーをゲートウェイバックエンド構成に自動的に変換します。サポートされる CEL 属性の完全な一覧については、agentgateway CEL referenceを参照してください。

  1. developers グループに属するすべてのユーザーのアクセスを制限するように、solo-docs と githubcopilot-mcp の Deployment を更新します。exists マクロは jwt.Groups 配列クレームを反復処理します。先頭の ! は結果を反転するため、developers グループに属していないすべてのユーザーに対して式が true になります。

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: solo-docs
    spec:
      targetRef:
        kind: MCPServer
        name: solo-docs
        tag: latest
      runtimeRef:
        name: mcp-gateway
        kind: Runtime
      runtimeConfig:
        route:
          pathSuffix: /solo-docs
        backend:
          mcp:
            authorization:
              action: Allow
              policy:
                matchExpressions:
                - '!jwt.Groups.exists(g, g == "developers")'
    ---
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: githubcopilot-mcp
    spec:
      targetRef:
        kind: MCPServer
        name: githubcopilot-mcp
        tag: latest
      runtimeRef:
        name: mcp-gateway
        kind: Runtime
      runtimeConfig:
        route:
          pathSuffix: /githubcopilot
        backend:
          mcp:
            authorization:
              action: Allow
              policy:
                matchExpressions:
                - '!jwt.Groups.exists(g, g == "developers")'
    EOF
    フィールド説明
    runtimeConfig.backend.mcp.authorization.actionいずれかの matchExpression が true と評価された場合、Allow はアクセスを許可します。どの式にも一致しないリクエストは拒否されます。
    matchExpressions[0]!jwt.Groups.exists(g, g == "developers") は、Groups クレームに developers が含まれていない呼び出し元に対して true と評価されます。

    出力例:

    ✓ Deployment/solo-docs updated
    ✓ Deployment/githubcopilot-mcp updated
    
  2. ユーザー名 dev-user、パスワード password でログインし、新しいトークンをエクスポートします。

    arctl user logout
    arctl user login
    export TOKEN=$(arctl user info --show-tokens | jq -r '.access_token')
  3. solo-docs へのセッションを初期化し、セッション ID を取得します。

    curl -s --max-time 5 \
      -D /tmp/mcp-headers.txt \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.1"}}}' \
      "http://${GATEWAY_IP}/registry/solo-docs" > /dev/null
    
    export SESSION_ID=$(grep -i "^mcp-session-id:" /tmp/mcp-headers.txt \
      | tr -d '\r' | awk '{print $2}')
    echo $SESSION_ID
  4. dev-user ユーザーが solo-docs MCP サーバー上のツールを呼び出せないことを確認します。ゲートウェイが CEL ポリシーに基づいてツール呼び出しを拒否する場合、制限が呼び出し元に見えないように、403 ではなく Unknown tool エラーを伴う 400 を返します。

    curl -s -o /dev/null -w "%{http_code}\n" \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -H "mcp-session-id: ${SESSION_ID}" \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"istio"}}}' \
      "http://${GATEWAY_IP}/registry/solo-docs"

    出力例:

    400
    
  5. dev-user ユーザーが引き続き mymcp 上のツールを呼び出せることを確認します。新しいセッションを初期化し、example_echo ツールを呼び出します。

    curl -s --max-time 5 \
      -D /tmp/mcp-headers.txt \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.1"}}}' \
      "http://${GATEWAY_IP}/registry/mymcp" > /dev/null
    
    export SESSION_ID=$(grep -i "^mcp-session-id:" /tmp/mcp-headers.txt \
      | tr -d '\r' | awk '{print $2}')
    
    curl -s -o /dev/null -w "%{http_code}\n" \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -H "mcp-session-id: ${SESSION_ID}" \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"example_echo","arguments":{"message":"hello"}}}' \
      "http://${GATEWAY_IP}/registry/mymcp"

    出力例:

    200
    

Tip

ポリシーが想定どおりに動作しない場合は、agctl proxy trace を使用してライブトレーシングウィンドウを開き、agentgateway プロキシが各リクエストで行う JWT 検証と CEL 認可の判断を確認します。agentgateway CLI install guideから agctl をインストールしてください。

agctl proxy trace gateway/agentgateway-proxy -n agentgateway-system

別のターミナルで、調査するリクエストを送信します。トレース出力には、抽出された JWT クレームや CEL 式が一致したか拒否されたかなど、各ポリシー評価ステップが表示されます。これは、クレームの不一致、期限切れトークン、CEL 式のエラーを特定する最も速い方法です。

ステップ 7: 特定ユーザーのツールアクセスを制限する

前のステップでは、ユーザーグループ全体に対して MCP サーバーレベルでアクセスを制限する方法を確認しました。特定の個別ユーザーに対して、ツールレベルでアクセスを制限することもできます。ユーザーの jwt.sub クレームに一致する backend.mcp.authorization CEL 式を、mymcp-remote Deployment に直接追加します。agentregistry リコンサイラーは、インラインポリシーをゲートウェイバックエンド構成に自動的に変換します。

このステップでは、mymcp-remote MCP サーバー上で dev-user を example_echo ツールのみに制限します。example_sum ツールへのアクセスは拒否されます。

Tip

このステップで 401 エラーが発生した場合は、トークンの有効期限が切れています。次のコマンドを実行してトークンを更新します。

arctl user logout

arctl user login export TOKEN=$(arctl user info –show-tokens | jq -r ‘.access_token’)

  1. JWT から sub クレームを抽出します。

    export DEV_USER_SUB=$(arctl user info --show-tokens \
      | jq -r '.access_token | split(".") | .[1] | @base64d | fromjson | .sub')
    echo $DEV_USER_SUB

    出力例:

    fbb5e028-4ef7-41de-8a6b-88365c26287d
    
  2. ユーザー単位のツール制限を追加するために、mymcp-remote Deployment を更新します。この例では、前に取得した $DEV_USER_SUB sub クレーム値と一致しないユーザーに完全なアクセスを許可します。ユーザーが $DEV_USER_SUB sub クレーム値と一致する場合は、example_echo ツールへのアクセスのみを許可します。その他のすべてのツールは拒否されます。YAML が arctl に送信される前にシェルが ${DEV_USER_SUB} をリテラル UUID に展開するため、レジストリに保存される CEL 式には変数名ではなく実際の sub クレーム値が含まれます。

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: mymcp-remote
    spec:
      targetRef:
        kind: MCPServer
        name: mymcp-remote
        tag: latest
      runtimeRef:
        name: mcp-gateway
        kind: Runtime
      runtimeConfig:
        route:
          pathSuffix: /mymcp
        backend:
          mcp:
            authorization:
              action: Allow
              policy:
                matchExpressions:
                - 'jwt.sub != "${DEV_USER_SUB}"'
                - 'jwt.sub == "${DEV_USER_SUB}" && mcp.tool.name == "example_echo"'
    EOF
    フィールド説明
    matchExpressions[0]jwt.sub != "${DEV_USER_SUB}" は、dev-user ではないすべてのユーザーに対して true と評価されます。制限対象ではないユーザーには、すべてのツールが許可されます。
    matchExpressions[1]jwt.sub == "${DEV_USER_SUB}" && mcp.tool.name == "example_echo" は、dev-user が example_echo ツールを呼び出す場合にのみ true と評価されます。このユーザーからのその他のツール呼び出しはいずれの式にも一致せず、拒否されます。

    出力例:

    ✓ Deployment/mymcp-remote updated
    
  3. mymcp-remote MCP サーバーへのセッションを初期化し、セッション ID を取得します。

    curl -s --max-time 5 \
      -D /tmp/mcp-headers.txt \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0.1"}}}' \
      "http://${GATEWAY_IP}/registry/mymcp" > /dev/null
    
    export SESSION_ID=$(grep -i "^mcp-session-id:" /tmp/mcp-headers.txt \
      | tr -d '\r' | awk '{print $2}')
    echo "Session ID: ${SESSION_ID}"
  4. dev-user が example_echo ツールを呼び出せることを確認します。

    curl -s -o /dev/null -w "%{http_code}\n" \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -H "mcp-session-id: ${SESSION_ID}" \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"example_echo","arguments":{"message":"hello"}}}' \
      "http://${GATEWAY_IP}/registry/mymcp"

    出力例:

    200
    
  5. dev-user が example_sum ツールを呼び出せないことを確認します。ゲートウェイが CEL ポリシーに基づいてツール呼び出しを拒否する場合、ポリシー適用が呼び出し元に見えないように、403 ではなく Unknown tool エラーを伴う 400 HTTP レスポンスを返します。

    curl -s -o /dev/null -w "%{http_code}\n" \
      -H "Authorization: Bearer ${TOKEN}" \
      -H "Content-Type: application/json" \
      -H "Accept: application/json, text/event-stream" \
      -H "mcp-session-id: ${SESSION_ID}" \
      -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"example_sum","arguments":{"a":1,"b":2}}}' \
      "http://${GATEWAY_IP}/registry/mymcp"

    出力例:

    400
    

ステップ 8: リクエストレートを制限する

親 HTTPRoute にローカルレートリミットポリシーを適用し、ユーザーが MCP サーバーに送信できるリクエスト数を制限します。上限を超えたリクエストは 429 Too Many Requests で拒否されます。CEL ディスクリプターを使用したツール単位のレートリミットなどの詳細については、agentgateway rate limiting docsを参照してください。

  1. 親 HTTPRoute にレートリミットポリシーを適用します。次のポリシーは、リクエストを 1 分あたり 1 件に制限し、短いバーストでは追加で 1 件のリクエストを許可します。

    kubectl apply -f - <<EOF
    apiVersion: enterpriseagentgateway.solo.io/v1alpha1
    kind: EnterpriseAgentgatewayPolicy
    metadata:
      name: mcp-gateway-rate-limit
      namespace: agentgateway-system
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: agentregistry-delegate
      traffic:
        rateLimit:
          local:
          - requests: 1
            unit: Minutes
            burst: 1
    EOF
    フィールド説明
    traffic.rateLimit.localレプリカ間で調整せず、プロキシインスタンスごとに適用されます。クラスター全体の制限が必要な場合は、外部レートリミットサービスとともに traffic.rateLimit.global を使用します。
    requestsunit あたりの継続的なリクエストレートです。
    burst継続レートを超える短いバーストで許可される追加リクエスト数です。
  2. ポリシーが受け入れられたことを確認します。

    kubectl get enterpriseagentgatewaypolicies -A

    出力例:

    NAMESPACE             NAME                    ACCEPTED   ATTACHED   AGE
    agentgateway-system   mcp-gateway-jwt-auth    True       True       10m
    agentgateway-system   mcp-gateway-rate-limit  True       True       30s
    

    ポリシーに Accepted: False が表示される場合は、kubectl describe enterpriseagentgatewaypolicy <name> -n agentgateway-system を実行して拒否理由を確認します。

  3. ステップ 7 のセッション ID を使用して、3 つの tools/call リクエストを連続して送信します。dev-user は example_echo ツールに制限されているため、このテストではそのツールを使用します。レートリミットでは 1 分あたり 1 件のリクエストと 1 件のバーストが許可されるため、最初の 2 件は成功し、3 件目は拒否されます。ステップ 7 以降に ${SESSION_ID} の有効期限が切れている場合は、続行する前にステップ 7 の initialize コマンドを再実行して新しいセッション ID を取得します。

    for i in 1 2 3; do
      echo -n "Request $i: "
      curl -s -o /dev/null -w "%{http_code}\n" \
        -H "Authorization: Bearer ${TOKEN}" \
        -H "Content-Type: application/json" \
        -H "Accept: application/json, text/event-stream" \
        -H "mcp-session-id: ${SESSION_ID}" \
        -d "{\"jsonrpc\":\"2.0\",\"id\":$i,\"method\":\"tools/call\",\"params\":{\"name\":\"example_echo\",\"arguments\":{\"message\":\"hello\"}}}" \
        "http://${GATEWAY_IP}/registry/mymcp"
    done

    出力例:

    Request 1: 200
    Request 2: 200
    Request 3: 429
    

    最初の 2 件のリクエストは成功します (基本 1 件 + バースト 1 件)。3 件目は 429 Too Many Requests で拒否されます。

これで、中央 agentgateway プロキシ経由で 3 つの MCP サーバーを公開し、すべての MCP トラフィックに JWT 認証を必須にし、ユーザーグループごとにカタログの可視性を制限し、JWT クレームを使用してユーザー単位のツールフィルタリングを適用し、不正利用を防ぐためにリクエストレートを制限できました。

次のステップ

MCP ゲートウェイが稼働したら、次に行いたいことに応じてパスを選択します。

その他の agentgateway ポリシーを確認する

このガイドで構成した agentgateway ポリシーについて、さらに詳しく確認します。

レジストリアクセス制御をさらに追加する

このガイドで扱った範囲を超えて、カタログレベルのアクセス制御を拡張します。