Skip to content
Latest (currently 2026.7.0) has the newest features, bug fixes, and CVE patches of Solo Enterprise for agentregistry.

Solo Enterprise for kagent

Page as Markdown

Set up Solo Enterprise for agentregistry and add your first agent to the registry catalog. Then, deploy your agent to the Kubernetes AI runtime in Solo Enterprise for kagent.

Solo Enterprise for agentregistry is an enterprise-grade AI artifact platform to manage the lifecycle of your AI artifacts, including AI agents, MCP tool servers, skills, and more. With Solo Enterprise for agentregistry, you can easily orchestrate deployments across runtimes, and monitor the efficiency and cost of your AI deployments. The product builds on the open-source agentregistry project. To learn more about Solo Enterprise for agentregistry, see About.

About this guide

This guide walks you through the full setup of Solo Enterprise for agentregistry with Solo Enterprise for kagent as a Kubernetes runtime. By the end, you have:

  • A Keycloak identity provider that runs in your cluster and is configured with the realm, clients, and users that Solo Enterprise for agentregistry and Solo Enterprise for kagent require for OIDC authentication.
  • An Solo Enterprise for agentregistry control plane that is secured with OIDC authentication and backed by PostgreSQL and ClickHouse.
  • Solo Enterprise for kagent running in your cluster, including the management plane and the kagent controller.
  • The arctl CLI that can authenticate with Solo Enterprise for agentregistry so that you can get started with managing and deploying AI artifacts.
  • A connection from the registry to Solo Enterprise for kagent so that you can deploy AI agents to your Kubernetes cluster.
  • A Python MCP server and an agent published to the registry and deployed to Solo Enterprise for kagent, so that the agent has access to MCP tools at runtime.

The following diagram shows the components you set up and how they relate to each other.

    graph LR
    subgraph k8s["Kubernetes cluster"]
        subgraph kc_ns["keycloak namespace"]
            KC["Keycloak<br/>(Steps 1–2)<br/>OIDC identity provider"]
        end
        subgraph ar_ns["agentregistry-system namespace"]
            AR["Solo Enterprise for agentregistry<br/>(Step 4)"]
        end
        subgraph kagent_ns["kagent namespace"]
            KM["Solo Enterprise for kagent<br/>(Step 3)<br/>management plane + controller"]
        end
        AR -. "Validate tokens<br/>(ar-backend)" .-> KC
        KM -. "Validate tokens<br/>(kagent-backend)" .-> KC
        AR -- "Deploy agent<br/>(Step 8)" --> KM
        AR -- "Deploy MCP server<br/>(Step 11)" --> KM
    end

    User(["User"]) -- "Log in via arctl<br/>(Step 5)" --> KC
    User -- "Publish agent<br/>(Step 7)" --> AR
    User -- "Deploy agent<br/>(Step 8)" --> AR
    User -- "Publish MCP server<br/>(Step 10)" --> AR
    User -- "Deploy MCP server<br/>(Step 11)" --> AR
  

Before you begin

  1. Install the following CLIs.

    • helm: Install the Solo Enterprise for agentregistry Helm chart.
    • kubectl: Interact with your Kubernetes cluster.
    • aws: Interact with your agent in AWS Bedrock AgentCore.
    • jq: Used as a script wrapper in this guide.
    • openssl: Generate a unique private key for JWT signing. Install with brew install openssl.
  2. Set up and start a Docker Engine on your local machine, such as Docker Desktop so that you can work with Kind clusters. Solo Enterprise for agentregistry also uses Docker compose to build Docker images and spin up AI artifacts.

  3. Create a Kind cluster.

    kind create cluster

Step 1: Configure Keycloak

Solo Enterprise for agentregistry does not bundle its own identity provider. Instead, it relies on an external OIDC provider to authenticate users, issue access tokens, and enforce role-based access control. In this quickstart, you use Keycloak as your OIDC provider.

Before you deploy Keycloak, you configure it by creating a ConfigMap that defines the realm, clients, protocol mappers, groups, and users that Solo Enterprise for agentregistry requires. Keycloak reads this file at startup and automatically creates the following resources, so you do not have to configure them manually through the Keycloak UI.

The ConfigMap defines the following resources:

ResourceNameDescription
RealmagentregistryA dedicated Keycloak realm that isolates agentregistry’s identity configuration from other applications. The registry server uses the realm’s issuer URL to discover OIDC endpoints.
Clientar-backendA confidential client that is used by the registry server to validate access tokens that are issued by the CLI and UI clients.
Clientar-cli-interactiveA public client for the arctl CLI. The client uses the device authorization grant (RFC 8628), which lets users log in from a terminal by opening a printed URL in a browser.
Clientar-cli-passwordA public client for scripted user authentication in CI/CD pipelines. Uses the password credentials grant with a username and password.
Clientar-uiA public client for the browser-based registry UI. This client uses the authorization code flow with PKCE. The redirectUris and webOrigins are set to * for local testing. In production deployments, these URLs must be replaced with the origin that your UI uses.
Clientkagent-backendA confidential client used by the kagent-enterprise controller and the solo-enterprise-ui backend to validate access tokens issued to kagent-ui. Tokens that include ar-backend in their aud claim are not accepted by kagent-enterprise — only tokens with kagent-backend in the audience are.
Clientkagent-uiA public client for the solo-enterprise-ui browser frontend. Uses the authorization code flow with PKCE.
ClientagentregistryA confidential service-account client that the agentregistry server uses to authenticate itself when calling the Solo Enterprise for kagent controller. When you create, update, or delete a deployment that targets the kagent runtime, the agentregistry server obtains its own access token from Keycloak via the OAuth2 client_credentials grant by using this client. The agentregistry server does not forward the end user’s bearer token to the Solo Enterprise for kagent controller. The client has a groups protocol mapper that adds the service account’s group memberships to the Groups claim in the minted token so that the controller can map the claim to an RBAC role in Solo Enterprise for kagent.
Protocol mappergroupsAn oidc-group-membership-mapper that writes the user’s Keycloak group memberships into the Groups JWT claim. The registry reads this claim to determine RBAC roles. The claim is added to access tokens that are issued for the ar-cli-interactive, ar-cli-password, and ar-ui clients.
Protocol mapperar-backend-audienceAn oidc-audience-mapper that adds ar-backend to the aud claim of every access token. Without the correct aud claim, the registry server rejects tokens with a 401 Unauthorized error, even when the user’s groups claim is correct, because the aud claim must match the backend client ID. The claim is added to access tokens that are issued for the ar-cli-interactive, ar-cli-password, and ar-ui clients.
Protocol mapperkagent-backend-audienceAn oidc-audience-mapper that adds kagent-backend to the aud claim of access tokens that are issued for the kagent-ui client. Without this mapper, Solo Enterprise for kagent rejects tokens from the UI with a 401 Unauthorized error.
GroupadminsA group that grants full admin access in the registry. Assigned to admin-user so they have superuser rights on first login.
GroupagentregistryA group assigned to the agentregistry service account. In this guide, the Solo Enterprise for kagent RBAC is configured to map membership in this group to the global.Writer role, which grants the agentregistry server permission to create, update, and delete Solo Enterprise for kagent resources. Use a role with the least permissions that allows you to perform all required operations in Solo Enterprise for kagent.
Useradmin-userA test admin user with the password password, assigned to the admins group.
Userservice-account-agentregistryA backing user that Keycloak automatically creates to represent the agentregistry client internally. When the agentregistry client requests a token via the client_credentials grant, Keycloak mints the token for this user and assigns it the agentregistry group at import time. This way, the token has the Groups: ["agentregistry"] claim that can be mapped to an RBAC role definition in Solo Enterprise for kagent.
  1. Create the Keycloak namespace.

    kubectl create namespace keycloak
  2. Create the ConfigMap with your Solo Enterprise for agentregistry OIDC configuration.

    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": "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

With the realm configuration in place, you can now deploy Keycloak itself. This step creates the Keycloak pod and exposes it through a load balancer service so that both the registry server and the arctl CLI can reach it. The --import-realm flag instructs Keycloak to load the ConfigMap from Step 1 on startup to apply all the realm, client, and user configuration automatically.

  1. Create the Keycloak deployment and service from the configuration in your ConfigMap.

    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: KEYCLOAK_ADMIN
              value: "admin"
            - name: KEYCLOAK_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
    EOF
  2. Wait for the Keycloak rollout to finish.

    kubectl -n keycloak rollout status deploy/keycloak
  3. Set the Keycloak endpoint details from the load balancer service. These environment variables are used throughout the rest of this guide. The KEYCLOAK_ISSUER is the OIDC discovery URL that the registry uses to find the token and JWKS endpoints. KEYCLOAK_URL is the base URL that is needed to retrieve the ar-backend client secret in the next step. If you are running locally in kind and need a local IP address for the load balancer service, consider using 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_ISSUER
  4. Get the client secrets that Keycloak automatically generated for the ar-backend, kagent-backend, and agentregistry clients. The registry server uses the ar-backend secret to verify user access tokens. The Solo Enterprise for kagent controller uses the kagent-backend secret to verify tokens that are issued to the kagent-ui client. The agentregistry secret is used by the registry server when it calls Solo Enterprise for kagent with its own service identity. You pass these secrets to the respective Helm charts in the next steps.

    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"

Step 3: Install Solo Enterprise for kagent

  1. Save your Solo Enterprise for kagent license key in an environment variable. To obtain the key, contact an account representative. If you prefer to specify the license key in a secret instead, see Licensing.

    export KAGENT_LICENSE_KEY="<key>"
  2. Save the API key for the LLM that you want to use as an environment variable. This example uses Gemini. For a list of supported LLMs, see Supported LLMs in the Solo Enterprise for kagent documentation.

    export GOOGLE_API_KEY="<key>"
  3. Set the Solo Enterprise for kagent patch version. This example uses the latest version. You can find other versions in the reference documentation.

    export KAGENT_ENT_VERSION=0.5.0
  4. Install the kagent-mgmt chart for Solo Enterprise for kagent.

    helm upgrade -i kagent-mgmt \
      oci://us-docker.pkg.dev/solo-public/solo-enterprise-helm/charts/management \
      -n kagent --create-namespace \
      --version "$KAGENT_ENT_VERSION" \
      --set cluster=mgmt-cluster \
      --set products.kagent.enabled=true \
      --set products.agentregistry.enabled=true \
      --set-string licensing.licenseKey="$KAGENT_LICENSE_KEY" \
      --set-string oidc.issuer="$KEYCLOAK_ISSUER" \
      --set-string ui.backend.oidc.clientId=kagent-backend \
      --set-string ui.backend.oidc.secret="$KAGENT_BACKEND_SECRET" \
      --set-string ui.frontend.oidc.clientId=kagent-ui
  5. Verify that the management component pods have a status of Running.

    kubectl get po -n kagent
    • The clickhouse pod runs a ClickHouse exporter for local data storage.
    • The ui pod runs the UI that you can use to monitor and manage agents.
    • The telemetry-collector pod runs the OTel collector that collects telemetry data from your cluster.
    NAME                                    READY   STATUS              RESTARTS   AGE
    kagent-mgmt-clickhouse-shard0-0         1/1     Running             0          4s
    solo-enterprise-telemetry-collector-0   1/1     Running             0          4s
    solo-enterprise-ui-76c85c7d6b-xftcj     5/5     Running             0          4s
    
  6. Install the kagent-enterprise CRDs chart.

    helm upgrade --install kagent-crds \
      oci://us-docker.pkg.dev/solo-public/kagent-enterprise-helm/charts/kagent-enterprise-crds \
      -n kagent \
      --version "$KAGENT_ENT_VERSION"
  7. Create an OBO token for the Solo Enterprise for kagent controller and store it in a Kubernetes secret. The controller watches for a secret named jwt in its own namespace and uses the token to provide authentication so that agents can execute actions on behalf of the user. If the secret is missing, the OBO token handler stays in a not ready state and A2A chat requests to agents fail with an upstream auth failed: obo token handler not ready error.

    openssl genrsa -out /tmp/key.pem 2048
    kubectl create secret generic jwt \
    -n kagent --from-file=jwt=/tmp/key.pem \
    --dry-run=client -o yaml | kubectl apply -f -    
  8. Create a values file for the RBAC configuration in Solo Enterprise for kagent.

    roleMappings is a lookup table that maps a Keycloak group name to a Solo Enterprise for kagent role. In this guide, any token that carries agentregistry in its Groups claim is granted the global.Writer role in Solo Enterprise for kagent. Tokens with other Groups claim values or tokens that do not have a Groups claim at all, are denied with a 401 Unauthorized response.

    cat > /tmp/kagent-rbac.yaml << 'EOF'
    rbac:
      roleMapping:
        roleMapper: 'has(claims.Groups) ? claims.Groups.transformList(i, v, v in rolesMap, rolesMap[v]) : []'
        roleMappings:
          agentregistry: global.Writer
    EOF
  9. Install the kagent-enterprise chart to deploy the Solo Enterprise for kagent controller and UI.

    helm upgrade -i kagent \
      oci://us-docker.pkg.dev/solo-public/kagent-enterprise-helm/charts/kagent-enterprise \
      -n kagent \
      --version "$KAGENT_ENT_VERSION" \
      --set-string licensing.licenseKey="$KAGENT_LICENSE_KEY" \
      --set agents.k8s-agent.enabled=true \
      --set kagent-tools.enabled=true \
      --set kmcp.licensing.createSecret=false \
      --set-string oidc.issuer="$KEYCLOAK_ISSUER" \
      --set oidc.clientId=kagent-backend \
      --set-string oidc.secret="$KAGENT_BACKEND_SECRET" \
      --set providers.openAI.apiKey=$GOOGLE_API_KEY \
      -f /tmp/kagent-rbac.yaml
  10. Verify that the controller and UI deployments are ready.

    kubectl get po -n kagent

    Example output:

    kagent-controller-dfc4c966c-zzds5                    1/1     Running                      0          3m
    kagent-mgmt-clickhouse-shard0-0                      1/1     Running                      0          5m48s
    kagent-postgresql-57c4f5597f-x4mjg                   1/1     Running                      0          3m
    kagent-tools-69bdb559df-cb9bw                        1/1     Running                      0          3m
    kmcp-enterprise-controller-manager-dbd59bcbf-j5hjt   1/1     Running                      0          3m
    solo-enterprise-telemetry-collector-0                1/1     Running                      0          5m48s
    solo-enterprise-ui-66cbb6687c-6czzk                  4/4     Running                      0          5m48s
    

Step 4: Install Solo Enterprise for agentregistry

Install Solo Enterprise for agentregistry and connect it to the Keycloak instance that you set up. The OIDC Helm values that you provide in this command point the registry server at your Keycloak realm that you configured. This way, the registry server knows what endpoint to use to validate access tokens, the client credentials to use, and the JWT claims that carry the role information that the registry server uses for RBAC decisions.

  1. Save your Solo Enterprise for agentregistry license key in an environment variable. To obtain the key, contact an account representative. For more options, such as providing the key in a secret, see Licensing.

    export AGENTREGISTRY_LICENSE_KEY=<license_key>
  2. Install Solo Enterprise for agentregistry.

    helm upgrade --install agentregistry \
      oci://us-docker.pkg.dev/solo-public/agentregistry-enterprise/helm/agentregistry-enterprise \
      --version 2026.7.0 \
      --namespace agentregistry-system \
      --create-namespace \
      --set licensing.createSecret=true \
      --set licensing.licenseKey=$AGENTREGISTRY_LICENSE_KEY \
      --set oidc.issuer=$KEYCLOAK_ISSUER \
      --set oidc.clientId=ar-backend \
      --set oidc.clientSecret=$AR_BACKEND_SECRET \
      --set oidc.publicClientId=ar-ui \
      --set oidc.roleClaim=Groups \
      --set oidc.superuserRole=admins \
      --set kagent.outboundAuth.oidc.clientId=agentregistry \
      --set-string kagent.outboundAuth.oidc.clientSecret=$AGENTREGISTRY_OUTBOUND_SECRET
    Helm valueDescription
    oidc.issuerThe issuer URL of your Keycloak realm. The registry uses this URL to discover the OIDC provider’s endpoints.
    oidc.clientIdThe confidential backend client ID (ar-backend). Used to validate incoming user tokens.
    oidc.clientSecretThe client secret for the ar-backend client.
    oidc.publicClientIdThe public client ID for the browser-based UI (ar-ui). The server uses this client to log in to the UI.
    oidc.roleClaimThe JWT claim that contains role or group names. Must match the Token Claim Name in the group mapper (Groups).
    oidc.superuserRoleThe group name that grants full admin access (admins).
    licensing.createSecretSet to true so that the chart creates the license secret from the licensing.licenseKey value.
    licensing.licenseKeyYour Solo Enterprise for agentregistry license key.
    kagent.outboundAuth.oidc.clientIdThe client ID that the registry server uses to obtain a new token from Keycloak before calling Solo Enterprise for kagent. If unset, Solo Enterprise for agentregistry falls back to the client that is defined in the oidc.clientId Helm setting.
    kagent.outboundAuth.oidc.clientSecretThe client secret for the agentregistry service-account client.
  3. Verify that the Solo Enterprise for agentregistry pods are up and running.

    kubectl get pods -n agentregistry-system

    Example output:

    NAME                                                            READY   STATUS    RESTARTS   AGE
    agentregistry-clickhouse-shard0-0                               1/1     Running   0          51s
    agentregistry-enterprise-postgresql-764898cbff-l8477            1/1     Running   0          51s
    agentregistry-enterprise-server-7d56d5b698-zxj8k                1/1     Running   0          51s
    agentregistry-enterprise-telemetry-collector-6ff77cbc4f-fgzwk   1/1     Running   0          51s
    
  4. Port-forward the Solo Enterprise for agentregistry controller on port 12121. The registry service is not exposed externally, so port-forwarding is required to reach it from your local machine. The arctl CLI and the UI both default to http://localhost:12121 as the registry endpoint.

    kubectl -n agentregistry-system port-forward svc/agentregistry-enterprise-server 12121:12121
  5. Open the Solo Enterprise for agentregistry UI and log in with the admin-user username and password password when prompted. Then, review the dashboard. In later steps, you add agents and MCP servers to your registry.

Step 5: Install the arctl CLI

The arctl CLI is the primary tool for managing AI artifacts in Solo Enterprise for agentregistry. You use it to create, publish, and deploy AI artifacts. Before you can run any arctl commands, you must authenticate the CLI with Keycloak to obtain a valid access token.

  1. Download and install the arctl CLI.

    curl -sSL https://storage.googleapis.com/agentregistry-enterprise/install.sh | ARCTL_VERSION=v2026.7.0 sh
    export PATH=$HOME/.arctl/bin:$PATH
  2. Log in to the registry. Choose between the interactive device authorization and the static username and password credential flow.

    Use this flow for interactive logins from a terminal. The CLI uses the ar-cli-interactive public client and starts a device authorization grant flow that prints a URL and a one-time code for you to approve in a browser.

    1. Configure the CLI to use the Keycloak issuer and ar-cli-interactive client. Then, log in to Solo Enterprise for agentregistry.

      export OIDC_ISSUER=$KEYCLOAK_ISSUER
      export OIDC_CLIENT_ID=ar-cli-interactive
      arctl user login

      Example output:

      To complete the login process, please:
       1. Open: http://172.18.0.13:8080/realms/agentregistry/device
       2. Enter the code: SYSQ-SNSP
       - or go to: http://172.18.0.13:8080/realms/agentregistry/device?user_code=SYSQ-SNSP
      
    2. Open the URL in a web browser and log into Keycloak with the admin-user username and password password. Confirming the code in the browser tells Keycloak to issue an access token for the CLI session. After authorization is complete, the CLI stores the token in your system keychain so you do not have to log in again until it expires.

    Use this flow for scripted logins where a specific user identity is required in CI/CD pipelines. The CLI uses the ar-cli-password public client and exchanges a username and password directly for an access token.

    1. Log in to Solo Enterprise for agentregistry with the admin-user/password credentials.

      export OIDC_ISSUER=$KEYCLOAK_ISSUER
      arctl user login \
        --oidc-flow=password-credentials \
        --oidc-client-id=ar-cli-password \
        --oidc-username=admin-user \
        --oidc-password=password
    2. Save the access token in the ARCTL_API_TOKEN environment variable.

      export ARCTL_API_TOKEN=$(arctl user info --show-tokens | jq -r .access_token)

  3. Verify that you can run arctl commands.

    arctl user whoami

    Example output:

    FIELD       VALUE
    Subject     8d7ffd9c-1f75-459a-b900-bfa4f5e3b492
    Email       admin-user@example.com
    Issuer      http://172.18.0.10:8080/realms/agentregistry
    Superuser   true (overrides role-based permission checks)
    
    ROLE     STATUS
    admins   no explicit permissions configured
    
    If you see 401 errors in the CLI, you might have previously stored a token in the ARCTL_API_TOKEN environment variable. This environment variable takes precedence over the token that you obtain via the CLI login flow. Try running unset ARCTL_API_TOKEN. Then, repeat the CLI login flow.

Step 6: Connect to kagent

  1. Create the Solo Enterprise for kagent runtime in Solo Enterprise for agentregistry. The runtime points to the Solo Enterprise for kagent controller that you deployed to the kagent namespace.

    arctl apply -f- <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Runtime
    metadata:
      name: kagent
    spec:
      type: Kagent
      telemetryEndpoint: http://agentregistry-enterprise-telemetry-collector.agentregistry-system.svc.cluster.local:4318
      config:
        kagentUrl: http://kagent-controller.kagent:8083
        namespace: kagent
    EOF

    Example output:

    ✓ Runtime/kagent applied
    
  2. List the runtimes that are connected to Solo Enterprise for agentregistry and verify that the kagent runtime is listed. You also see a default kubernetes-default and local runtime.

    arctl get runtimes

    Example output:

    NAME                 TYPE
    kagent               Kagent
    kubernetes-default   Kubernetes
    local                Local 
    virtual-default      Virtual
    

Step 7: Publish an agent

In this step, you use the built-in scaffolding capability to create a Python agent with Google’s Agent Development Kit (ADK) that uses the Gemini LLM provider. Then, you build the agent image and publish the agent in the Solo Enterprise for agentregistry catalog.

  1. Create an agent.

    The following command creates the myagent Python agent with the Google ADK agent framework that is configured to use the Gemini provider. When you run the command, a myagent directory is created on your local machine that contains the scaffold for your agent. You see the directory structure in your CLI output.

    arctl init agent myagent --framework adk --language python  --model-provider gemini --model-name gemini-2.5-flash
    Want to see available options as you configure your agent? Try out the interactive CLI mode that walks you through the details for your agent scaffold. Simply run arctl init agent to start the CLI in interactive mode.

    Example output:

    ✓ Created agent: myagent (framework: adk, language: python, model: gemini/gemini-2.5-flash)
    
    🚀 Next steps:
     1. Run locally (optional):
        arctl run myagent
        (export GOOGLE_API_KEY in your shell or set it in .env first)
     2. Publish to the registry:
        arctl apply -f myagent/agent.yaml
    
  2. Explore the agent scaffold that was created for you. You can optionally make changes to the files to customize your agent further.

    ls myagent
    FileDescription
    arctl.yamlThe agent details that you specified with the arctl CLI.
    agent.yamlThe agent definition, including the agent framework, LLM provider, model, and agent image location. If you add MCP servers or skills to an agent, they are added to the agent definition.
    docker-compose.yamlA Docker compose file that is used to spin up and run your agent on your local machine when you use the arctl agent run command.
    DockerfileThe Dockerfile to spin up and run your agent in a containerized environment.
    myagentA directory that includes the agent.py script that defines the agent, including the provider and model that you want to use. The directory also includes the agent card for agent discovery and any MCP tools or skills that the agent has access to.
    otel-collector-config.yamlAn OpenTelemetry configuration that instructs the agent where to send telemetry data, including traces, logs, and metrics.
    pyproject.tomlThe dependency definition of your agent.
    README.mdAn introduction to the agent that you created with instructions for how to further customize it.
  3. Review the image location and tag that was set for the agent in the agent.yaml file.

    Each AI runtime requires the agent image to be packaged in a specific way so that the agent can be run in the runtime’s environment. To deploy an agent to Solo Enterprise for kagent, you need to have a Docker container image for the agent that is publicly available so that it can be pulled during an agent deployment. The Docker image location is stored in the agent.yaml file that is automatically created with an agent scaffold and represents the agent definition. Note that if you already have an agent image in a container registry, you must create the agent.yaml file yourself to describe the agent that you want to add to the catalog.

    cat myagent/agent.yaml

    Example output:

    apiVersion: ar.dev/v1alpha1
    kind: Agent
    metadata:
      name: myagent
    spec:
      description: myagent agent
      modelName: gemini-2.5-flash
      modelProvider: gemini
      source:
        image: localhost:5001/myagent:latest
    
    By default, agentregistry uses localhost:5001 as the container registry and latest as the image tag, such as localhost:5001/myagent:latest. If you want to use agentregistry to push images to your own container registry, make sure to update this file with the registry and image tag that you want to use. Note that this update is not required if you want to build images locally only without pushing them to your container registry.
  4. Build the agent image on your local machine. The following command builds the image with the Dockerfile that is included in your myagent scaffold and tags it with the container registry and version information that is provided in the agent.yaml file.

    arctl build myagent
    To also use Solo Enterprise for agentregistry to push the image to your container registry, include the --push option. You can also set the platform, for which you want to build the image, such as linux/amd64 by using the --platform option. Note that you must log in to your container registry first, such as by using docker login for DockerHub. For more information, see the arctl build command.

    Example output:

    Building agent image: localhost:5001/myagent:latest
    ✓ Successfully built Docker image: localhost:5001/myagent:latest
    
  5. Verify that the image is built.

    docker images | grep myagent 

    Example output:

    localhost:5001/myagent latest  f7ba3e98adaa   11 minutes ago     2.93GB
    
  6. If you did not push the image to a container registry, load the Docker image to your cluster. This step is required in local test setups, such as kind, to test the deployment of your agent later. Note that this step is not required if you pushed the image to a container image registry that your cluster can pull from.

    kind load docker-image localhost:5001/myagent:latest --name <cluster-name>
  7. Publish the agent to the Solo Enterprise for agentregistry catalog. To publish an agent, you simply apply the agent.yaml file that you reviewed earlier.

    arctl apply -f myagent/agent.yaml

    Example output:

    → Injecting labels from arctl.yaml: arctl.dev/framework=adk, arctl.dev/language=python
    ✓ Agent/myagent (latest) configured
    
  8. Verify that the agent is published in the catalog.

    arctl get agents

    Example output:

    NAME      TAG      PROVIDER   MODEL
    myagent   latest   gemini     gemini-2.5-flash
    
  9. Optional: Open the registry UI to review your published agent.

Step 8: Deploy the agent

With the agent published to the registry catalog, you can now deploy it to Solo Enterprise for kagent.

  1. If you have not done so yet, save your Gemini API key as an environment variable. To retrieve your API key, log in to the Google AI Studio and select API Keys. This key is required to interact with the agent through the Gemini LLM provider later.

    export GOOGLE_API_KEY=<apikey>
  2. Create a Deployment for the agent. The registry spins up a Kubernetes pod for your agent from the Docker image reference that is stored in the registry catalog. The env setting passes the Gemini API key through to the agent container so it can call the Gemini API after it is deployed.

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: myagent       # must match agent's metadata.name
    spec:
      targetRef:
        kind: Agent
        name: myagent
      runtimeRef:
        kind: Runtime
        name: kagent
      env:
        GOOGLE_API_KEY: "$GOOGLE_API_KEY"
    EOF
    SettingDescription
    targetRefThe AI artifact that you want to deploy. In this example, you deploy the myagent agent version 0.1.0 that is published in the registry catalog.
    runtimeRefThe name of the runtime that you want to deploy to.
    envPass the Gemini API key through to the agent container as an environment variable so it can call the Gemini API after it is deployed.

    Example output:

    ✓ Deployment/myagent created
    
  3. Check the agent deployment in your cluster and make sure that the pod is up and running.

    kubectl get pods -n kagent | grep myagent

    Example output:

    myagent-85bf9949c9-gp49h   1/1     Running                      0          5m35s
    
  4. Open the Instances view in the UI and select the myagent agent that you deployed. You can monitor the progress of the deployment in the Instance Logs section.

Step 9: Chat with the agent

You can chat with the agent by using the Solo Enterprise for agentregistry UI.

  1. Open the Instances view in the UI and select the myagent agent.

  2. In the Agent Details card, click Open Chat.

  3. Chat with the agent. For example, you can ask it what it can do for you. Then, hit Enter and wait for the agent to reply.

  4. Go to the Tracing view in the UI and verify that you can see a trace for the request that you sent to the agent.

  5. Select a trace to see the details of the trace.

Step 10: Publish an MCP server

In this step, you create a Python MCP server, run it locally to verify that it works, and then build and publish it to the registry. You later explore how to add the MCP server to your agent.

  1. Create an MCP server.

    The following command creates a mymcp FastMCP Python server with an echo tool. When you run the command, a mymcp directory is created on your local machine that contains the scaffold for your MCP server. You also see the port that is configured for the MCP server. Note the port as you need it later when connecting to the MCP server by using the MCP Inspector tool.

    arctl init mcp mymcp --framework fastmcp --language python
    Want to see available options as you configure your MCP server? Try out the interactive CLI mode that walks you through the details for your agent scaffold. Simply run arctl init mcp to start the CLI in interactive mode.

    Example output:

    ✓ Created MCP server: mymcp (framework: fastmcp, language: python, port: 3000)
    
    🚀 Next steps:
      1. Run locally (optional):
         arctl run mymcp
      2. Publish to the registry:
         arctl apply -f mymcp/mcp.yaml
    
  2. Explore the MCP server scaffold that was created for you. You can optionally make changes to the files to customize your MCP server further.

    ls -R mymcp

    Example output:

    Dockerfile     mcp.yaml       pyproject.toml README.md      src            tests
    
    mymcp/src:
    core    main.py tools
    
    mymcp/src/core:
    __init__.py server.py   utils.py
    
    mymcp/src/tools:
    __init__.py echo.py sum.py
    
    mymcp/tests:
    test_discovery.py test_server.py    test_tools.py
    FileDescription
    arctl.yamlThe MCP server details that you specified with the arctl CLI.
    DockerfileThe Dockerfile to spin up and run your MCP server in a containerized environment.
    mcp.yamlThe MCP server configuration file that defines server metadata, transport settings, version, and other server-specific configuration.
    pyproject.tomlThe Python project configuration file that defines project dependencies, build settings, and metadata for the MCP server.
    README.mdAn introduction to the MCP server that you created with instructions for how to further customize it.
    srcA directory that contains the details of the MCP server, such as supported tools and the Python script to bootstrap and run the server.
    src.coreA directory that contains the MCP server source code files.
    src.toolsA directory that defines the tools that the MCP server can use. The sample scaffold includes an echo and a sum tool.
    testsA directory that contains generated tests.
  3. Run the MCP server on your local machine. The following command builds the MCP server image on your local machine and then spins up the server. In your CLI output, you see the path that your MCP server is exposed on. In the following example, the MCP server address is http://localhost:3000/mcp.

    arctl run mymcp

    Example output:

    ...
    → fastmcp-python: docker run --rm -p 3000:3000 localhost:5001/mymcp:latest --transport http --host 0.0.0.0 --port 3000
    2026-05-14 18:39:46,327 - INFO - Loaded tool module: sum
    2026-05-14 18:39:46,329 - INFO - Loaded tool module: echo
    2026-05-14 18:39:46,329 - INFO - 📦 Successfully loaded 2 tools
    2026-05-14 18:39:46,549 - INFO - HTTP Request: GET https://pypi.org/pypi/fastmcp/json "HTTP/1.1 200 OK"
    ...
    
    [05/14/26 18:39:46] INFO     Starting MCP server 'mymcp' with   transport.py:301
                              transport 'http' on                                
                              http://0.0.0.0:3000/mcp                
    
  4. Open the MCP inspector tool.

    npx modelcontextprotocol/inspector#0.21.2
  5. Connect to your MCP server. Enter the following details.

    • Transport type: Select Streamable HTTP.
    • URL: The MCP server URL that you retrieved earlier, such as http://localhost:3000/mcp.
    • Click Connect.
  6. Try out an MCP tool.

    1. In the MCP Inspector tool, click Connect.
    2. Navigate to the Tools tab and click List Tools. Verify that you see the example_sum and example_echo tool.
    3. Choose the example_echo tool and enter any string into the message field.
    4. Click Run Tool to execute the tool. Verify that you see your message echoed back to you.

  7. Exit the MCP Inspector by using the ctrl + c keys.

  8. Review the image location and tag that was set for the MCP server in the mcp.yaml file.

    Each AI runtime requires the MCP server image to be packaged in a specific way so that the server can be run in the runtime’s environment. To deploy an MCP Server to Solo Enterprise for kagent, you need to have a Docker container image for the server that is publicly available so that it can be pulled during an MCP server deployment. The Docker image location is stored in the mcp.yaml file that is automatically created with an MCP server scaffold and represents the MCP server definition. Note that if you already have an MCP server image in a container registry, you must create the mcp.yaml file yourself to describe the server that you want to add to the catalog.

    cat mymcp/mcp.yaml

    Example output:

    apiVersion: ar.dev/v1alpha1
    kind: MCPServer
    metadata:
      name: mymcp
    spec:
      description: mymcp MCP server
      source:
        package:
          launch:
            args:
            - type: positional
              value: src/main.py
            - type: positional
              value: --transport
            - type: positional
              value: http
            - type: positional
              value: --host
            - type: positional
              value: 0.0.0.0
            - type: positional
              value: --port
            - type: positional
              value: "3000"
            command: python3
          origin:
            identifier: localhost:5001/mymcp:latest
            oci:
              serverName: mymcp
            type: oci
          transport:
            path: /mcp
            port: 3000
            type: http
      title: mymcp
    
    By default, agentregistry uses localhost:5001 as the container registry and latest as the image tag, such as localhost:5001/mymcp:latest. If you want to use agentregistry to push images to your own container registry, make sure to update this file with the registry and image tag that you want to use. Note that this update is not required if you want to build images locally only without pushing them to your container registry.
  9. Build the MCP server Docker image.

    arctl build mymcp 

    Example output:

    Building MCP server image: localhost:5001/mymcp:latest
    Building Docker image for python project...
    ...
    #24 naming to localhost:5001/mymcp:latest done
    #24 unpacking to localhost:5001/mymcp:latest done
    #24 DONE 0.0s
    ✓ Successfully built Docker image: localhost:5001/mymcp:latest
    
  10. If you did not push the image to a container registry, load the Docker image to your cluster. This step is required in local test setups, such as kind, to test the deployment of your MCP server later. Note that this step is not required if you pushed the image to a container image registry that your cluster can pull from.

    kind load docker-image localhost:5001/mymcp:latest --name <cluster-name>
  11. Publish the MCP server to the Solo Enterprise for agentregistry catalog.

    arctl apply -f mymcp/mcp.yaml

    Example output:

    → Injecting labels from arctl.yaml: arctl.dev/framework=fastmcp, arctl.dev/language=python
    ✓ MCPServer/mymcp (latest) created
    
  12. Verify that the MCP server is published in the catalog.

    arctl get mcp

    Example output:

    NAME    TAG      DESCRIPTION
    mymcp   latest   mymcp MCP server
    
  13. Optional: Open the registry UI to review your published MCP server.

Step 11: Deploy the MCP server

With the MCP server published to the registry catalog, you can now deploy it to Solo Enterprise for kagent.

  1. Deploy the MCP server to Solo Enterprise for kagent. The registry spins up a Kubernetes pod for your MCP server from the Docker image reference that is stored in the registry catalog.

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: mymcp
    spec:
      targetRef:
        kind: MCPServer
        name: mymcp
      runtimeRef:
        kind: Runtime
        name: kagent
    EOF

    Example output:

    ✓ Deployment/mymcp created
    
  2. Check the MCP server deployment in your cluster and make sure that the pod is up and running.

    kubectl get pods -n kagent | grep mymcp

    Example output:

    myorg-mymcp-5b695845df-vwbqn     1/1     Running                      0          14s
    
  3. Open the Instances view in the UI and select the mymcp MCP server that you deployed. You can monitor the progress of the deployment in the Instance Logs section.

Step 12: Add the MCP server to the agent

Add the MCP server to the agent so that the agent can access the MCP server tools during runtime.

  1. Update myagent/agent.yaml to reference the MCP server that you published earlier. The following command overwrites the file with the mcpServers field added to the agent spec. This tells the registry that the agent requires access to the MCP server’s tools when deployed. You also update the version number for the agent.

    cat > myagent/agent.yaml << 'EOF'
    apiVersion: ar.dev/v1alpha1
    kind: Agent
    metadata:
      name: myagent
    spec:
      description: myagent agent
      mcpServers:
      - kind: MCPServer
        name: mymcp
        tag: latest
      modelName: gemini-2.5-flash
      modelProvider: gemini
      source:
        image: localhost:5001/myagent:latest
    EOF
  2. Re-build the agent image.

    arctl build myagent
  3. If you did not push the image to a container registry, load the Docker image to your cluster.

    kind load docker-image localhost:5001/myagent:latest --name <cluster-name>
  4. Publish the new version of the agent to the Solo Enterprise for agentregistry catalog.

    arctl apply -f myagent/agent.yaml

    Example output:

    → Injecting labels from arctl.yaml: arctl.dev/framework=adk, arctl.dev/language=python
    ✓ Agent/myagent (latest) configured
    
  5. Re-deploy the agent.

    arctl apply -f - <<EOF
    apiVersion: ar.dev/v1alpha1
    kind: Deployment
    metadata:
      name: myagent  
    spec:
      targetRef:
        kind: Agent
        name: myagent
        tag: "latest"
      runtimeRef:
        kind: Runtime
        name: kagent
      env:
        GOOGLE_API_KEY: "$GOOGLE_API_KEY"
    EOF
  6. Open the Instances view in the UI and select the myagent agent.

  7. In the Agent Details card, click Open Chat.

  8. Chat with the agent. For example, you can ask it what it can do for you. Then, hit Enter and wait for the agent to reply. Verify that you see the built-in tools, roll a dice and check prime numbers, and that you also see the MCP echo and sum tools.

Congratulations! You successfully set up Solo Enterprise for agentregistry, created AI artifacts, and deployed them to Solo Enterprise for kagent.

Next steps

Explore other AI runtimes.