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 サーバーを公開して保護する
agentgateway バーチャルランタイムを通じて複数のリモート MCP サーバーを公開し、ユーザー、グループ、ツールごとにアクセスを制限する方法を学びます。
中央プロキシがない場合、MCP サーバーはインフラストラクチャ全体に分散し、共有認証、一貫した検出エンドポイント、どのユーザーがどのサーバーに到達でき、特定のツールを呼び出せるかを強制する方法がありません。サーバー URL に到達できる MCP クライアントは、そのすべてのツールへ完全にアクセスできます。Solo Enterprise for agentregistry は、MCP サーバーの前段に配置される単一のポリシー適用プロキシである agentgateway バーチャルランタイムでこの問題を解決し、AI アーティファクトカタログの他の部分と同じ方法でアクセスを管理できるようにします。
agentgateway は、MCP トラフィック用のロードバランサーと考えることができます。ゲートウェイ管理者がデリゲーションルールを一度付与すると、レジストリユーザーはゲートウェイ構成に触れずに、その背後の MCP サーバーを追加または削除できます。各サーバーには、プロキシ経由の安定したルーティング可能な URL が割り当てられます。アクセスポリシーは、どのユーザーグループが何を表示でき、デプロイ済みエージェントが何を呼び出せるかを制御します。
このガイドについて
このガイドでは、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 が必要です。これらのコンポーネントをまだセットアップしていない場合は、まず次の手順を完了してください。
OIDC プロバイダーとして Keycloak をセットアップします。必要な realm 設定、クライアント、シークレットを含めて Keycloak をインストールするために、必ず General setup タブの手順に従ってください。
Solo Enterprise for agentregistry をインストールし、
arctlCLI をインストールします。次のコマンドを実行して、ログイン済みであることを確認します。
arctl user whoamiまだ実行していない場合は、Solo Enterprise for agentregistry サーバーをポート 12121 でポートフォワードします。
kubectl -n agentregistry-system port-forward svc/agentregistry-enterprise-server 12121:12121Solo 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 サーバーを使用します。
GitHub で personal access token を作成し、環境変数に保存します。詳細については、GitHub docsを参照してください。
arctl user whoamiGitHub 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 サーバーをスキャフォールドし、ビルドしてクラスターにデプロイしてから、カタログに登録します。
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)MCP サーバーのコンテナイメージをビルドします。
arctl build mymcp出力例:
Building MCP server image: localhost:5001/mymcp:latest ✓ Successfully built Docker image: localhost:5001/mymcp:latestKind クラスターを使用している場合は、イメージをクラスターにロードします。それ以外の場合は、ビルドしたイメージにクラスターからアクセスできることを確認します。
kubectl -n agentregistry-system port-forward svc/agentregistry-enterprise-server 12121:12121MCP サーバーをクラスターにデプロイします。
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 EOFMCP サーバーの Pod が実行中であることを確認します。
kubectl get pods -n mcp-test出力例:
NAME READY STATUS RESTARTS AGE mymcp-6f7d9b8c4d-xvpzk 1/1 Running 0 30sクラスター内サーバーをカタログに登録します。
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 サーバーをゲートウェイで公開する
前のステップで登録した各 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 createdarctl 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 createdarctl 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 プレフィックスに追加されるパスです。 /で始める必要があり、ランタイムごとに一意である必要があります。すべてのデプロイが準備完了であることを確認します。
status.details.agentgateway.exposedAtフィールドには、MCP サーバーが公開されているゲートウェイ URL が含まれます。次のステップでは、これらの URL を使用して MCP サーバーアクセスをテストします。arctl get deployments -o yamlSolo.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がないか、/で始まっていません。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 2mgw-be-mcpserver-*: MCPServer カタログエントリごとに 1 つ作成されます。そのサーバーのアップストリームバックエンド構成を表します。gw-dep-*: カタログ Deployment ごとに 1 つ作成されます。デプロイされた各インスタンスのルーティングエントリを構成します。
ステップ 3: MCP サーバーアクセスを確認する
MCP Inspector を使用して、ゲートウェイが各リモート MCP サーバーにトラフィックをルーティングすることを確認します。
MCP Inspector ツールを開きます。
npx modelcontextprotocol/inspector#0.21.2トランスポートタイプとして Streamable HTTP を選択し、
status.details.agentgateway.exposedAtフィールドの MCP サーバー用ゲートウェイ URL を入力して、Connect をクリックします。http://172.18.0.4/registry/solo-docsなど、Solo.io docs サーバーの URL を入力します。Tools タブに移動し、List Tools をクリックします。
search、get_chunks、get_full_pageツールが表示されることを確認します。ツールを選択して試します。
http://172.18.0.13/registry/githubcopilotなど、GitHub Copilot サーバーの URL を入力します。Tools タブに移動し、List Tools をクリックします。GitHub ツールが表示されることを確認します。
get_meツールを選択して Run Tool をクリックします。GitHub ユーザー情報が表示されることを確認します。
http://172.18.0.13/registry/mymcpなど、クラスター内サーバーの URL を入力します。Tools タブに移動し、List Tools をクリックします。
example_echoツールとexample_sumツールが表示されることを確認します。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を参照してください。
発行者 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リモート 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 UnauthorizedHTTP レスポンスで拒否します。トークンが存在する場合にクレームを抽出しつつ、認証されていないリクエストも許可するには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を調整してください。ポリシーが 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フィールドの拒否理由を確認します。Gateway リソースのステータスからゲートウェイ IP アドレスをエクスポートします。
export GATEWAY_IP=$(kubectl get gateway agentgateway-proxy -n agentgateway-system \ -o jsonpath='{.status.addresses[0].value}') echo $GATEWAY_IPゲートウェイが認証されていないリクエストを拒否することを確認します。
Authorizationヘッダーなしで、ゲートウェイプロキシ上で公開した MCP サーバーにリクエストを送信し、ゲートウェイが401 UnauthorizedHTTP レスポンスを返すことを確認します。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有効な 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 グループと、そのグループに所属するユーザーを作成します。
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)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"}'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}"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"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を参照してください。
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ユーザー名
dev-user、パスワードpasswordでログインし、新しいトークンをエクスポートします。arctl user logout arctl user login export TOKEN=$(arctl user info --show-tokens | jq -r '.access_token')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_IDdev-userユーザーがsolo-docsMCP サーバー上のツールを呼び出せないことを確認します。ゲートウェイが 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"出力例:
400dev-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 logoutarctl user login export TOKEN=$(arctl user info –show-tokens | jq -r ‘.access_token’)
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ユーザー単位のツール制限を追加するために、
mymcp-remoteDeployment を更新します。この例では、前に取得した$DEV_USER_SUBsub クレーム値と一致しないユーザーに完全なアクセスを許可します。ユーザーが$DEV_USER_SUBsub クレーム値と一致する場合は、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 updatedmymcp-remoteMCP サーバーへのセッションを初期化し、セッション 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}"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"出力例:
200dev-userがexample_sumツールを呼び出せないことを確認します。ゲートウェイが CEL ポリシーに基づいてツール呼び出しを拒否する場合、ポリシー適用が呼び出し元に見えないように、403ではなくUnknown toolエラーを伴う400HTTP レスポンスを返します。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を参照してください。
親 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継続レートを超える短いバーストで許可される追加リクエスト数です。 ポリシーが受け入れられたことを確認します。
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を実行して拒否理由を確認します。ステップ 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 ポリシーについて、さらに詳しく確認します。
レジストリアクセス制御をさらに追加する
このガイドで扱った範囲を超えて、カタログレベルのアクセス制御を拡張します。