For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Keycloak
Keycloak を OIDC プロバイダーとして設定し、Solo Enterprise for agentregistry でユーザーを認証します。
Solo Enterprise for agentregistry は ID プロバイダー (IdP) によって保護されているため、認可されたユーザーだけが AI アーティファクトにアクセス、公開、デプロイできます。このガイドでは、IdP として Keycloak をデプロイし、必要な OIDC クライアントを作成して、レジストリサーバーと CLI がそれらを使用するように設定する手順を説明します。
始める前に
次の CLI をインストールします。
- helm: Install the Solo Enterprise for agentregistry Helm chart。
Docker Desktop などの Docker Engine をローカルマシンにセットアップして起動し、Kind クラスターを扱えるようにします。Solo Enterprise for agentregistry は、Docker イメージのビルドと AI アーティファクトの起動にも Docker Compose を使用します。
Kubernetes クラスターを作成するか、既存のクラスターを使用します。ローカルのテスト環境では、Kind クラスターを作成できます。
kind create cluster
ステップ 1: Keycloak を構成する
Solo Enterprise for agentregistry は独自の ID プロバイダーをバンドルしていません。代わりに、外部の OIDC プロバイダーを使用してユーザーを認証し、アクセストークンを発行し、ロールベースのアクセス制御を適用します。
Keycloak をデプロイする前に、Solo Enterprise for agentregistry に必要なレルム、クライアント、プロトコルマッパー、グループ、ユーザーを定義する ConfigMap を作成して設定します。Keycloak は起動時にこの ConfigMap を読み取り、これらのリソースを自動的に作成するため、Keycloak UI から手動で設定する必要はありません。
OIDC クライアントの構成は、Solo Enterprise for agentregistry を接続するランタイムによって異なります。
次の ConfigMap は、Solo Enterprise for agentregistry を AWS Bedrock AgentCore、Azure AI Foundry、Microsoft Copilot Studio、および Solo Enterprise for agentgateway を基盤とする仮想ランタイムに接続するために必要なリソースを定義します。
| Resource | Name | Description |
|---|---|---|
| Realm | agentregistry | agentregistry の ID 設定を他のアプリケーションから分離する専用の Keycloak レルムです。レジストリサーバーは、このレルムの発行者 URL を使用して OIDC エンドポイントを検出します。 |
| Client | ar-backend | CLI クライアントと UI クライアントが発行したアクセストークンを検証するために、レジストリサーバーが使用するコンフィデンシャルクライアントです。 |
| Client | ar-cli-interactive | arctl CLI 用のパブリッククライアントです。デバイス認可付与 (RFC 8628) を使用するため、ユーザーは表示された URL をブラウザーで開くことでターミナルからログインできます。 |
| Client | ar-cli-password | CI/CD パイプラインでスクリプト化されたユーザー認証を行うためのパブリッククライアントです。ユーザー名とパスワードによるパスワード認証情報グラントを使用します。 |
| Client | ar-ui | ブラウザーベースのレジストリ UI 用のパブリッククライアントです。PKCE を使用した認可コードフローを使用します。redirectUris と webOrigins は、ローカルテスト用に * に設定されています。本番環境では、UI が実際に使用するオリジンに置き換えてください。 |
| Client | ar-mcp-client | Claude Code や Codex などの MCP クライアント用のパブリッククライアントです。PKCE を使用する認可コードフローを使用します。リダイレクト URI は http://localhost:* に制限されるため、ローカル MCP クライアントは Dynamic Client Registration を使用せずに OAuth コールバックを受信できます。 |
| Protocol mapper | groups | ユーザーの Keycloak グループメンバーシップを Groups JWT クレームに書き込む oidc-group-membership-mapper です。レジストリはこのクレームを読み取り、RBAC ロールを判定します。ar-cli-interactive、ar-cli-password、ar-ui、ar-mcp-client クライアントに対して発行されるアクセストークンに追加されます。 |
| Protocol mapper | ar-backend-audience | アクセストークンの aud クレームに ar-backend を追加する oidc-audience-mapper です。正しい aud クレームがない場合、レジストリサーバーは 401 Unauthorized エラーでトークンを拒否します。ar-cli-interactive、ar-cli-password、ar-ui、ar-mcp-client クライアントに対して発行されるアクセストークンに追加されます。 |
| Group | admins | レジストリで完全な管理者アクセスを付与するグループです。admin-user に割り当てられるため、初回ログイン時からスーパーユーザー権限を持ちます。 |
| User | admin-user | パスワードが password のテスト用管理ユーザーで、admins グループに割り当てられています。 |
Keycloak ネームスペースを作成します。
kubectl create namespace keycloakSolo Enterprise for agentregistry の OIDC 設定を含む ConfigMap を作成します。
kubectl apply -f- <<EOF apiVersion: v1 kind: ConfigMap metadata: name: keycloak-agentregistry-realm namespace: keycloak data: agentregistry-realm.json: | { "realm": "agentregistry", "enabled": true, "clients": [ { "clientId": "ar-backend", "enabled": true, "publicClient": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false }, { "clientId": "ar-cli-interactive", "enabled": true, "publicClient": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "attributes": { "oauth2.device.authorization.grant.enabled": "true" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "ar-cli-password", "enabled": true, "publicClient": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": true, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "ar-ui", "enabled": true, "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["*"], "webOrigins": ["*"], "attributes": { "pkce.code.challenge.method": "S256" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "ar-mcp-client", "enabled": true, "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["http://localhost:*"], "webOrigins": ["http://localhost:*"], "attributes": { "pkce.code.challenge.method": "S256" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] } ], "groups": [ {"name": "admins"} ], "users": [ { "username": "admin-user", "email": "admin-user@example.com", "firstName": "Admin", "lastName": "User", "enabled": true, "credentials": [ {"type": "password", "value": "password", "temporary": false} ], "groups": ["admins"] } ] } EOF
次の ConfigMap は、Solo Enterprise for agentregistry を Solo Enterprise for kagent に接続する構成に必要なリソースを定義します。この構成には、kagent 管理プレーン UI 用のクライアントが追加で含まれます。
| Resource | Name | Description |
|---|---|---|
| Realm | agentregistry | agentregistry の ID 設定を他のアプリケーションから分離する専用の Keycloak レルムです。レジストリサーバーは、このレルムの発行者 URL を使用して OIDC エンドポイントを検出します。 |
| Client | ar-backend | CLI クライアントと UI クライアントが発行したアクセストークンを検証するために、レジストリサーバーが使用するコンフィデンシャルクライアントです。 |
| Client | ar-cli-interactive | arctl CLI 用のパブリッククライアントです。デバイス認可付与 (RFC 8628) を使用するため、ユーザーは表示された URL をブラウザーで開くことでターミナルからログインできます。 |
| Client | ar-cli-password | CI/CD パイプラインでスクリプト化されたユーザー認証を行うためのパブリッククライアントです。ユーザー名とパスワードによるパスワード認証情報グラントを使用します。 |
| Client | ar-ui | ブラウザーベースのレジストリ UI 用のパブリッククライアントです。PKCE を使用した認可コードフローを使用します。redirectUris と webOrigins は、ローカルテスト用に * に設定されています。本番環境では、UI が実際に使用するオリジンに置き換えてください。 |
| Client | ar-mcp-client | Claude Code や Codex などの MCP クライアント用のパブリッククライアントです。PKCE を使用する認可コードフローを使用します。リダイレクト URI は http://localhost:* に制限されるため、ローカル MCP クライアントは Dynamic Client Registration を使用せずに OAuth コールバックを受信できます。 |
| Client | kagent-backend | kagent-ui に発行されたアクセストークンを検証するために、kagent-enterprise コントローラーと solo-enterprise-ui バックエンドが使用するコンフィデンシャルクライアントです。aud クレームに ar-backend が含まれるトークンは受け付けられず、オーディエンスに kagent-backend が含まれるトークンだけが受け付けられます。 |
| Client | kagent-ui | solo-enterprise-ui ブラウザーフロントエンド用のパブリッククライアントです。PKCE を使用した認可コードフローを使用します。 |
| Protocol mapper | groups | ユーザーの Keycloak グループメンバーシップを Groups JWT クレームに書き込む oidc-group-membership-mapper です。ar-cli-interactive、ar-cli-password、ar-ui、ar-mcp-client、kagent-ui クライアントに対して発行されるアクセストークンに追加されます。 |
| Protocol mapper | ar-backend-audience | ar-cli-interactive、ar-cli-password、ar-ui、ar-mcp-client クライアントに対して発行されるアクセストークンの aud クレームに ar-backend を追加する oidc-audience-mapper です。正しい aud クレームがない場合、レジストリサーバーは 401 Unauthorized エラーでトークンを拒否します。 |
| Protocol mapper | kagent-backend-audience | kagent-ui クライアント向けに発行されるアクセストークンの aud クレームに kagent-backend を追加する oidc-audience-mapper です。これがないと、kagent-enterprise は UI からのトークンを 401 Unauthorized エラーで拒否します。 |
| Client | agentregistry | Solo Enterprise for kagent コントローラーを呼び出すときに、agentregistry サーバーが自身を認証するために使用するコンフィデンシャルなサービスアカウントクライアントです。kagent ランタイムを対象とするデプロイを作成、更新、削除すると、agentregistry サーバーはこのクライアントを使用して OAuth2 client_credentials グラント経由で Keycloak からアクセストークンを取得します。このクライアントには groups プロトコルマッパーがあり、サービスアカウントのグループメンバーシップを Groups クレームに追加するため、kagent コントローラーはそれを RBAC ロールにマッピングできます。 |
| Group | admins | レジストリで完全な管理者アクセスを付与するグループです。admin-user に割り当てられるため、初回ログイン時からスーパーユーザー権限を持ちます。 |
| Group | agentregistry | service-account-agentregistry ユーザーに割り当てられます。kagent の RBAC はこのグループのメンバーシップを global.Writer ロールにマッピングし、agentregistry サーバーに kagent リソースの作成、更新、削除の権限を付与します。 |
| User | admin-user | パスワードが password のテスト用管理ユーザーで、admins グループに割り当てられています。 |
| User | service-account-agentregistry | agentregistry クライアントを内部的に表すために Keycloak が自動的に作成するバックエンドユーザーです。インポート時に agentregistry グループに割り当てられるため、client_credentials グラントで発行されたトークンには Groups: ["agentregistry"] クレームが含まれます。 |
Keycloak ネームスペースを作成します。
kubectl create namespace keycloakSolo Enterprise for agentregistry の OIDC 設定を含む ConfigMap を作成します。
kubectl apply -f- <<EOF apiVersion: v1 kind: ConfigMap metadata: name: keycloak-agentregistry-realm namespace: keycloak data: agentregistry-realm.json: | { "realm": "agentregistry", "enabled": true, "accessTokenLifespan": 86400, "ssoSessionIdleTimeout": 86400, "ssoSessionMaxLifespan": 86400, "clients": [ { "clientId": "ar-backend", "enabled": true, "publicClient": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false }, { "clientId": "ar-cli-interactive", "enabled": true, "publicClient": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "attributes": { "oauth2.device.authorization.grant.enabled": "true" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "ar-cli-password", "enabled": true, "publicClient": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": true, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "ar-ui", "enabled": true, "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["*"], "webOrigins": ["*"], "attributes": { "pkce.code.challenge.method": "S256" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "ar-mcp-client", "enabled": true, "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["http://localhost:*"], "webOrigins": ["http://localhost:*"], "attributes": { "pkce.code.challenge.method": "S256" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "ar-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "ar-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "kagent-backend", "enabled": true, "publicClient": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false }, { "clientId": "kagent-ui", "enabled": true, "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["*"], "webOrigins": ["*"], "attributes": { "pkce.code.challenge.method": "S256" }, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "true", "access.token.claim": "true", "userinfo.token.claim": "true" } }, { "name": "kagent-backend-audience", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.client.audience": "kagent-backend", "id.token.claim": "false", "access.token.claim": "true" } } ] }, { "clientId": "agentregistry", "enabled": true, "publicClient": false, "serviceAccountsEnabled": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "protocolMappers": [ { "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-group-membership-mapper", "consentRequired": false, "config": { "claim.name": "Groups", "full.path": "false", "id.token.claim": "false", "access.token.claim": "true", "userinfo.token.claim": "false" } } ] } ], "groups": [ {"name": "admins"}, {"name": "agentregistry"} ], "users": [ { "username": "admin-user", "email": "admin-user@example.com", "firstName": "Admin", "lastName": "User", "enabled": true, "credentials": [ {"type": "password", "value": "password", "temporary": false} ], "groups": ["admins"] }, { "username": "service-account-agentregistry", "enabled": true, "serviceAccountClientId": "agentregistry", "groups": ["agentregistry"] } ] } EOF
Step 2: Deploy Keycloak
レルムの設定が完了したら、Keycloak をデプロイします。このステップでは Keycloak Pod を作成し、ロードバランサーサービスを通じて公開して、レジストリサーバーと arctl CLI の両方から到達できるようにします。--import-realm フラグを指定すると、Keycloak は起動時にステップ 1 の ConfigMap を読み込み、レルム、クライアント、ユーザーのすべての設定を自動的に適用します。
Keycloak のデプロイとサービスを作成します。
kubectl apply -f- <<EOF apiVersion: v1 kind: Service metadata: name: keycloak namespace: keycloak labels: app: keycloak spec: ports: - name: http port: 8080 targetPort: 8080 selector: app: keycloak type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment metadata: name: keycloak namespace: keycloak labels: app: keycloak spec: replicas: 1 selector: matchLabels: app: keycloak template: metadata: labels: app: keycloak spec: containers: - name: keycloak image: quay.io/keycloak/keycloak:26.1.3 args: ["start-dev", "--import-realm"] env: - name: KC_BOOTSTRAP_ADMIN_USERNAME value: "admin" - name: KC_BOOTSTRAP_ADMIN_PASSWORD value: "admin" - name: PROXY_ADDRESS_FORWARDING value: "true" - name: KC_PROXY value: "edge" ports: - name: http containerPort: 8080 readinessProbe: httpGet: path: /realms/master port: 8080 volumeMounts: - name: realm-config mountPath: /opt/keycloak/data/import volumes: - name: realm-config configMap: name: keycloak-agentregistry-realm EOFKeycloak のロールアウトが完了するまで待ちます。
kubectl -n keycloak rollout status deploy/keycloakロードバランサーサービスから Keycloak のエンドポイント情報を設定します。これらの環境変数は、このガイドの残りの部分で使用します。
KEYCLOAK_ISSUERは、レジストリがトークンエンドポイントと JWKS エンドポイントを見つけるために使用する OIDC 検出 URL です。Kind でローカルに実行していて、ロードバランサーサービスにローカル IP アドレスが必要な場合は、cloud-provider-kind の使用を検討してください。export ENDPOINT_KEYCLOAK=$(kubectl -n keycloak get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}'):8080 export HOST_KEYCLOAK=$(echo ${ENDPOINT_KEYCLOAK} | cut -d: -f1) export PORT_KEYCLOAK=$(echo ${ENDPOINT_KEYCLOAK} | cut -d: -f2) export KEYCLOAK_URL=http://${ENDPOINT_KEYCLOAK} export KEYCLOAK_REALM=agentregistry export KEYCLOAK_ISSUER=$KEYCLOAK_URL/realms/${KEYCLOAK_REALM} echo $KEYCLOAK_ISSUERKeycloak が自動生成したクライアントシークレットを取得します。これらのシークレットは、次の手順でそれぞれの Helm チャートに渡します。
ar-backendクライアントシークレットを取得します。レジストリサーバーは、このシークレットを使用してアクセストークンを検証します。export KEYCLOAK_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) export AR_BACKEND_ID=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=ar-backend" \ | jq -r '.[0].id') export AR_BACKEND_SECRET=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${AR_BACKEND_ID}/client-secret" \ | jq -r .value) echo "AR_BACKEND_SECRET: $AR_BACKEND_SECRET"ar-backend、kagent-backend、agentregistryの各クライアントシークレットを取得します。レジストリサーバーは、アクセストークンの検証にar-backendシークレットを使用します。kagent-enterpriseコントローラーは、kagent-uiクライアントに発行されたトークンの検証にkagent-backendシークレットを使用します。agentregistryシークレットは、レジストリサーバーが自身のサービス ID で Solo Enterprise for kagent を呼び出すときに使用されます。export KEYCLOAK_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) export AR_BACKEND_ID=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=ar-backend" \ | jq -r '.[0].id') export AR_BACKEND_SECRET=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${AR_BACKEND_ID}/client-secret" \ | jq -r .value) echo "AR_BACKEND_SECRET: $AR_BACKEND_SECRET" export KAGENT_BACKEND_ID=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=kagent-backend" \ | jq -r '.[0].id') export KAGENT_BACKEND_SECRET=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${KAGENT_BACKEND_ID}/client-secret" \ | jq -r .value) echo "KAGENT_BACKEND_SECRET: $KAGENT_BACKEND_SECRET" export AGENTREGISTRY_OUTBOUND_ID=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=agentregistry" \ | jq -r '.[0].id') export AGENTREGISTRY_OUTBOUND_SECRET=$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" \ "${KEYCLOAK_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${AGENTREGISTRY_OUTBOUND_ID}/client-secret" \ | jq -r .value) echo "AGENTREGISTRY_OUTBOUND_SECRET: $AGENTREGISTRY_OUTBOUND_SECRET"