Solo Enterprise for kagent
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
arctlCLI 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
Install the following CLIs.
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.
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:
| Resource | Name | Description |
|---|---|---|
| Realm | agentregistry | A 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. |
| Client | ar-backend | A confidential client that is used by the registry server to validate access tokens that are issued by the CLI and UI clients. |
| Client | ar-cli-interactive | A 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. |
| Client | ar-cli-password | A public client for scripted user authentication in CI/CD pipelines. Uses the password credentials grant with a username and password. |
| Client | ar-ui | A 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. |
| Client | kagent-backend | A 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. |
| Client | kagent-ui | A public client for the solo-enterprise-ui browser frontend. Uses the authorization code flow with PKCE. |
| Client | agentregistry | A 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 mapper | groups | An 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 mapper | ar-backend-audience | An 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 mapper | kagent-backend-audience | An 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. |
| Group | admins | A group that grants full admin access in the registry. Assigned to admin-user so they have superuser rights on first login. |
| Group | agentregistry | A 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. |
| User | admin-user | A test admin user with the password password, assigned to the admins group. |
| User | service-account-agentregistry | A 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. |
Create the Keycloak namespace.
kubectl create namespace keycloakCreate 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.
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 EOFWait for the Keycloak rollout to finish.
kubectl -n keycloak rollout status deploy/keycloakSet the Keycloak endpoint details from the load balancer service. These environment variables are used throughout the rest of this guide. The
KEYCLOAK_ISSUERis the OIDC discovery URL that the registry uses to find the token and JWKS endpoints.KEYCLOAK_URLis the base URL that is needed to retrieve thear-backendclient 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_ISSUERGet the client secrets that Keycloak automatically generated for the
ar-backend,kagent-backend, andagentregistryclients. The registry server uses thear-backendsecret to verify user access tokens. The Solo Enterprise for kagent controller uses thekagent-backendsecret to verify tokens that are issued to thekagent-uiclient. Theagentregistrysecret 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
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>"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>"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.0Install the
kagent-mgmtchart 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-uiVerify that the management component pods have a status of
Running.kubectl get po -n kagent- The
clickhousepod runs a ClickHouse exporter for local data storage. - The
uipod runs the UI that you can use to monitor and manage agents. - The
telemetry-collectorpod 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- The
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"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
jwtin 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 anot readystate and A2A chat requests to agents fail with anupstream auth failed: obo token handler not readyerror.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 -Create a values file for the RBAC configuration in Solo Enterprise for kagent.
roleMappingsis a lookup table that maps a Keycloak group name to a Solo Enterprise for kagent role. In this guide, any token that carriesagentregistryin itsGroupsclaim is granted theglobal.Writerrole in Solo Enterprise for kagent. Tokens with otherGroupsclaim values or tokens that do not have aGroupsclaim 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 EOFInstall the
kagent-enterprisechart 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.yamlVerify that the controller and UI deployments are ready.
kubectl get po -n kagentExample 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.
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>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_SECRETHelm value Description 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-backendclient.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 trueso that the chart creates the license secret from thelicensing.licenseKeyvalue.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.clientIdHelm setting.kagent.outboundAuth.oidc.clientSecretThe client secret for the agentregistryservice-account client.Verify that the Solo Enterprise for agentregistry pods are up and running.
kubectl get pods -n agentregistry-systemExample 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 51sPort-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
arctlCLI and the UI both default tohttp://localhost:12121as the registry endpoint.kubectl -n agentregistry-system port-forward svc/agentregistry-enterprise-server 12121:12121Open the Solo Enterprise for agentregistry UI and log in with the
admin-userusername andpasswordpassword 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.
Download and install the
arctlCLI.curl -sSL https://storage.googleapis.com/agentregistry-enterprise/install.sh | ARCTL_VERSION=v2026.7.0 sh export PATH=$HOME/.arctl/bin:$PATHLog 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-interactivepublic client and starts a device authorization grant flow that prints a URL and a one-time code for you to approve in a browser.Configure the CLI to use the Keycloak issuer and
ar-cli-interactiveclient. Then, log in to Solo Enterprise for agentregistry.export OIDC_ISSUER=$KEYCLOAK_ISSUER export OIDC_CLIENT_ID=ar-cli-interactive arctl user loginExample 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-SNSPOpen the URL in a web browser and log into Keycloak with the
admin-userusername andpasswordpassword. 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-passwordpublic client and exchanges a username and password directly for an access token.Log in to Solo Enterprise for agentregistry with the
admin-user/passwordcredentials.export OIDC_ISSUER=$KEYCLOAK_ISSUER arctl user login \ --oidc-flow=password-credentials \ --oidc-client-id=ar-cli-password \ --oidc-username=admin-user \ --oidc-password=passwordSave the access token in the
ARCTL_API_TOKENenvironment variable.export ARCTL_API_TOKEN=$(arctl user info --show-tokens | jq -r .access_token)
Verify that you can run
arctlcommands.arctl user whoamiExample 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 configuredIf you see401errors in the CLI, you might have previously stored a token in theARCTL_API_TOKENenvironment variable. This environment variable takes precedence over the token that you obtain via the CLI login flow. Try runningunset ARCTL_API_TOKEN. Then, repeat the CLI login flow.
Step 6: Connect to kagent
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
kagentnamespace.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 EOFExample output:
✓ Runtime/kagent appliedList the runtimes that are connected to Solo Enterprise for agentregistry and verify that the
kagentruntime is listed. You also see a defaultkubernetes-defaultandlocalruntime.arctl get runtimesExample 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.
Create an agent.
The following command creates the
myagentPython agent with the Google ADK agent framework that is configured to use the Gemini provider. When you run the command, amyagentdirectory 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-flashWant 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 runarctl init agentto 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.yamlExplore the agent scaffold that was created for you. You can optionally make changes to the files to customize your agent further.
ls myagentFile Description arctl.yamlThe agent details that you specified with the arctlCLI.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 runcommand.DockerfileThe Dockerfile to spin up and run your agent in a containerized environment. myagentA directory that includes the agent.pyscript 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. Review the image location and tag that was set for the agent in the
agent.yamlfile.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.yamlfile 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 theagent.yamlfile yourself to describe the agent that you want to add to the catalog.cat myagent/agent.yamlExample 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:latestBy default, agentregistry useslocalhost:5001as the container registry andlatestas the image tag, such aslocalhost: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.Build the agent image on your local machine. The following command builds the image with the Dockerfile that is included in your
myagentscaffold and tags it with the container registry and version information that is provided in theagent.yamlfile.arctl build myagentTo also use Solo Enterprise for agentregistry to push the image to your container registry, include the--pushoption. You can also set the platform, for which you want to build the image, such aslinux/amd64by using the--platformoption. Note that you must log in to your container registry first, such as by usingdocker loginfor 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:latestVerify that the image is built.
docker images | grep myagentExample output:
localhost:5001/myagent latest f7ba3e98adaa 11 minutes ago 2.93GBIf 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>Publish the agent to the Solo Enterprise for agentregistry catalog. To publish an agent, you simply apply the
agent.yamlfile that you reviewed earlier.arctl apply -f myagent/agent.yamlExample output:
→ Injecting labels from arctl.yaml: arctl.dev/framework=adk, arctl.dev/language=python ✓ Agent/myagent (latest) configuredVerify that the agent is published in the catalog.
arctl get agentsExample output:
NAME TAG PROVIDER MODEL myagent latest gemini gemini-2.5-flashOptional: 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.
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>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
envsetting 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" EOFSetting Description targetRefThe AI artifact that you want to deploy. In this example, you deploy the myagentagent 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 createdCheck the agent deployment in your cluster and make sure that the pod is up and running.
kubectl get pods -n kagent | grep myagentExample output:
myagent-85bf9949c9-gp49h 1/1 Running 0 5m35sOpen the Instances view in the UI and select the
myagentagent 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.
Open the Instances view in the UI and select the
myagentagent.In the Agent Details card, click Open Chat.
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.
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.
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.
Create an MCP server.
The following command creates a
mymcpFastMCP Python server with an echo tool. When you run the command, amymcpdirectory 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 pythonWant 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 runarctl init mcpto 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.yamlExplore 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 mymcpExample 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.pyFile Description arctl.yamlThe MCP server details that you specified with the arctlCLI.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. 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 mymcpExample 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/mcpOpen the MCP inspector tool.
npx modelcontextprotocol/inspector#0.21.2Connect 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.
Try out an MCP tool.
- In the MCP Inspector tool, click Connect.
- Navigate to the Tools tab and click List Tools. Verify that you see the
example_sumandexample_echotool. - Choose the
example_echotool and enter any string into the message field. - Click Run Tool to execute the tool. Verify that you see your message echoed back to you.


Exit the MCP Inspector by using the
ctrl + ckeys.Review the image location and tag that was set for the MCP server in the
mcp.yamlfile.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.yamlfile 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 themcp.yamlfile yourself to describe the server that you want to add to the catalog.cat mymcp/mcp.yamlExample 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: mymcpBy default, agentregistry useslocalhost:5001as the container registry andlatestas the image tag, such aslocalhost: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.Build the MCP server Docker image.
arctl build mymcpExample 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:latestIf 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>Publish the MCP server to the Solo Enterprise for agentregistry catalog.
arctl apply -f mymcp/mcp.yamlExample output:
→ Injecting labels from arctl.yaml: arctl.dev/framework=fastmcp, arctl.dev/language=python ✓ MCPServer/mymcp (latest) createdVerify that the MCP server is published in the catalog.
arctl get mcpExample output:
NAME TAG DESCRIPTION mymcp latest mymcp MCP serverOptional: 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.
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 EOFExample output:
✓ Deployment/mymcp createdCheck the MCP server deployment in your cluster and make sure that the pod is up and running.
kubectl get pods -n kagent | grep mymcpExample output:
myorg-mymcp-5b695845df-vwbqn 1/1 Running 0 14sOpen the Instances view in the UI and select the
mymcpMCP 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.
Update
myagent/agent.yamlto reference the MCP server that you published earlier. The following command overwrites the file with themcpServersfield added to the agentspec. 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 EOFRe-build the agent image.
arctl build myagentIf 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>Publish the new version of the agent to the Solo Enterprise for agentregistry catalog.
arctl apply -f myagent/agent.yamlExample output:
→ Injecting labels from arctl.yaml: arctl.dev/framework=adk, arctl.dev/language=python ✓ Agent/myagent (latest) configuredRe-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" EOFOpen the Instances view in the UI and select the
myagentagent.In the Agent Details card, click Open Chat.
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.