Skip to main content

MCP Server

The Akuity MCP Server lets MCP-compatible AI agents work with the Akuity Platform, Argo CD, and Kargo through authenticated tools. On a self-hosted installation running v0.34.0 or later, every organization can use it without a license change. It is served by your own portal and instance hostnames.

This page covers the parts that are specific to self-hosted installations: the portal URL, identity provider registration, outbound network access, private certificate authorities, instance endpoint checks, and monitoring. Everything else works as described in the MCP Server section:

Requirements​

  • Akuity Platform v0.34.0 or later. Your existing license works; MCP does not need a license change.
  • portal.url set to the HTTPS URL that agents use to reach the portal, with no path component. MCP clients refuse a plaintext authorization server, and the OAuth callback the portal registers with your identity provider is derived from this value.
  • HTTPS hostnames for Argo CD and Kargo instances. The wildcard certificate from Getting Started covers them.
  • Outbound HTTPS access from the portal server to your identity provider and, for some clients, to the client's metadata host. See Outbound network access.

Every organization can use MCP, but the organization's platform endpoint and each instance's MCP access are separate settings, both off by default. An authorized user turns them on as described in Configure MCP access.

Enable MCP​

  1. If the portal signs users in through your own identity provider or Auth0, register the MCP callback and set the matching sso.oidc.mcpClientID or sso.auth0.mcpClientID Helm value before upgrading. See Portal sign-in. Bundled Dex needs no extra registration.

  2. Upgrade the installation to v0.34.0 or later. On a multi-region installation, upgrade every region before you turn on MCP access for instances, because a region that still runs an older release does not publish the instance's /mcp route.

  3. In the portal, open Organization Settings → MCP Access. Turn on the platform endpoint and the instances agents may reach, then click Save. An instance serves its /mcp route only after MCP access is turned on for that instance and the platform controller reconciles it. See Check instance endpoints.

  4. Connect a client with the snippets the portal shows on the MCP Access page. They use your platform endpoint, https://<PORTAL_HOSTNAME>/mcp, in place of the https://akuity.cloud/mcp endpoint shown in the general client instructions. The Akuity agent plugin takes the same endpoint, for example claude plugin install akuity@akuity --config endpoint=https://<PORTAL_HOSTNAME>/mcp, and codex mcp add akuity --url https://<PORTAL_HOSTNAME>/mcp points Codex at it.

Users must have signed in to the portal at least once before they connect an MCP client. The first portal sign-in creates the user account; an MCP sign-in for an unknown user fails with user not found.

Portal sign-in​

Interactive sign-in on the platform endpoint uses the same identity provider as portal login. What you need to register depends on how the portal is configured.

Portal loginWhat to do
Bundled Dex under the portal URL (sso.dex.enabled: true, sso.dex.issuerSubPath: true)Nothing to register. The chart declares a public PKCE client named akuity-platform-mcp with the callback <portal.url>/mcp/oauth/callback, and the portal server reaches Dex inside the cluster.
Bundled Dex on its own host (sso.dex.enabled: true, sso.dex.issuerSubPath: false)Nothing to register. The chart declares the same client. Serve the Dex ingress over HTTPS, because agents are sent to it to sign in.
Your own OIDC provider (sso.dex.enabled: false)Create a separate public application that uses PKCE and has no client secret. Register https://<PORTAL_HOSTNAME>/mcp/oauth/callback as its only redirect URI and set sso.oidc.mcpClientID to its client ID. sso.oidc.issuer must be an HTTPS URL.
Auth0 (sso.auth0.enabled: true)Create a separate public application that uses PKCE, add https://<PORTAL_HOSTNAME>/mcp/oauth/callback to its allowed callback URLs, and set sso.auth0.mcpClientID to its client ID.

Register the callback once per portal hostname. Individual MCP client callbacks are never registered with the identity provider: the portal validates them and presents only its own callback upstream.

Do not reuse the portal's login client as the MCP client. For direct OIDC, leaving sso.oidc.mcpClientID empty makes MCP sign-in unavailable. See the SSO parameters for the chart values.

Microsoft Entra ID (direct OIDC)​

Use two app registrations in the same tenant:

  1. Keep the portal login app's Web redirect URI at https://<PORTAL_HOSTNAME>/api/auth/callback. Its client ID and secret go in sso.oidc.clientID and sso.oidc.clientSecret.
  2. Create a separate MCP app. Under Authentication → Add a platform → Mobile and desktop applications, add https://<PORTAL_HOSTNAME>/mcp/oauth/callback as a custom redirect URI. This app uses Authorization Code with PKCE and no client secret. Put its client ID in sso.oidc.mcpClientID. It needs only OIDC sign-in permissions.
  3. On both apps, add the email optional claim to the ID token under Token configuration. Confirm that the user's directory account has an email address and that the issued ID token contains it. Requesting the email scope alone does not guarantee an email claim. Without it, portal login can fail with no_email.
  4. Use the tenant-specific issuer and disable bundled Dex and Auth0:
portal:
url: https://akuity.example.com

sso:
oidc:
enabled: true
issuer: https://login.microsoftonline.com/<TENANT_ID>/v2.0
clientID: <PORTAL_CLIENT_ID>
clientSecret: <PORTAL_LOGIN_CLIENT_SECRET>
mcpClientID: <MCP_CLIENT_ID>
scopes: "openid,profile,email,offline_access"
dex:
enabled: false
auth0:
enabled: false

Entra sends groups through the app's token-claim configuration; do not add a groups scope to this example. For registration details, see Microsoft's redirect URI and optional claims guides. The callbacks for direct Argo CD and Kargo MCP endpoints are configured separately under Instance sign-in.

Bundled Dex with Okta​

This example uses Dex's OIDC connector to sign in through Okta. In Okta, create an OIDC Web Application for the connector, enable the Authorization Code grant, and register https://<PORTAL_HOSTNAME>/dex/callback as its sign-in redirect URI. Assign the users or groups who may sign in. Configure the app to return an email and the groups needed for role mapping.

The two callbacks serve different connections: Okta redirects to /dex/callback, and Dex redirects MCP sign-ins to /mcp/oauth/callback. The chart registers the MCP client in Dex; you do not create another MCP app in Okta for this setup.

Supply sso.dex.secret.data.OKTA_CLIENT_SECRET through your secret manager. The chart stores it in the dex Kubernetes Secret and makes it available to the connector as an environment variable. Keep secret values out of version control, and keep the portal's existing client ID and secret when adding the connector.

portal:
url: https://akuity.example.com

sso:
oidc:
enabled: true
clientID: <PORTAL_CLIENT_ID>
clientSecret: <PORTAL_LOGIN_CLIENT_SECRET>
scopes: "openid,profile,email,offline_access,groups"
auth0:
enabled: false
dex:
enabled: true
issuerSubPath: true
secret:
data:
OKTA_CLIENT_SECRET: <OKTA_CONNECTOR_CLIENT_SECRET>
config:
connectors:
- type: oidc
id: okta
name: Okta
config:
issuer: https://<OKTA_DOMAIN>
clientID: <OKTA_CONNECTOR_CLIENT_ID>
clientSecret: $OKTA_CLIENT_SECRET
redirectURI: https://akuity.example.com/dex/callback
scopes: [openid, profile, email, groups]
getUserInfo: true
insecureEnableGroups: true

Use your Okta authorization server's exact issuer; a custom authorization server includes /oauth2/<AUTHORIZATION_SERVER_ID>. Configure its groups claim and access policy for the connector app. Keep email verification enabled and confirm that Okta supplies a verified email.

Dex's insecureEnableGroups allows the OIDC connector to pass group claims through. Those claims can remain stale across refreshes; users may need to sign in again after their groups change. See the Dex OIDC connector documentation.

note

The bundled Dex appends sso.dex.config after its own staticClients list. Put connectors and other settings there, but do not add a second staticClients key, because it can replace the list that contains the portal login client and akuity-platform-mcp.

For multiple regions, read the bundled-Dex limitation before deploying this configuration outside the primary region.

Auth0 setup​

Disable OIDC and bundled Dex when selecting Auth0. If an OIDC client ID is still configured through the enabled OIDC settings, the portal uses OIDC before Auth0.

sso:
oidc:
enabled: false
dex:
enabled: false
auth0:
enabled: true
domain: example.us.auth0.com
audience: <AUDIENCE>
clientID: <PORTAL_CLIENT_ID>
cliClientID: <CLI_CLIENT_ID>
mcpClientID: <MCP_CLIENT_ID>

Use a Regular Web Application for portal login and a separate Native application for MCP. Set the MCP application's authentication method to None, allow Authorization Code and Refresh Token grants, and register the callback shown above. The account's email must be verified.

If the API identified by sso.auth0.audience uses per-app authorization, grant the portal application User-Delegated Access under Applications → APIs → Application Access. Enable Allow Offline Access on the API for refresh tokens. Basic portal and MCP sign-in needs no Auth0 Management API permissions. See Auth0 application access.

The current chart does not render AUTH0_CLIENT_SECRET. Supply the portal client secret through an existing Kubernetes Secret, then add a Secret reference to the portal Deployment through your post-renderer or Kustomize layer. For example, create Secret auth0-portal with key clientSecret using your secret manager, and save this patch as auth0-portal-env.yaml:

spec:
template:
spec:
containers:
- name: portal-server
env:
- name: AUTH0_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: auth0-portal
key: clientSecret
kubectl --context <CONTEXT> --namespace akuity-platform patch deployment portal-server \
--type strategic --patch-file auth0-portal-env.yaml

Keep the patch in your deployment configuration and reapply it after Helm upgrades. Do not put the secret in portal.env, because that value is rendered in a ConfigMap.

Scopes and group mappings​

With your own OIDC provider, MCP sign-in requests sso.oidc.scopes, the same scopes portal login uses. If the provider requires a scope to return groups, include it before using sso.roleFromGroups; bundled Dex requires groups. Check that both the portal and MCP apps receive the groups claim. Auth0 group mapping uses the ID-token claim https://auth.akuity.io/groups, which can be set by a post-login Action.

Global sso.roleFromGroups mappings apply to every organization in the installation. Use explicit organization membership when access should be limited to one organization. The current sso.roleFromGroups.admin setting is ignored; use supported member/owner membership and assign workspace permissions explicitly instead.

Instance sign-in​

Direct instance endpoints use the instance's own authentication. The instance MCP Access page shows the exact callback and client to register.

  • Argo CD with oidc.config: register https://<ARGO_CD_HOSTNAME>/mcp/oauth/callback on the public client. The portal hands out cliClientID, or clientID when no clientSecret is set. Without a public client the direct endpoint is unavailable.
  • Kargo with OIDC: register https://<KARGO_HOSTNAME>/mcp/oauth/callback on cliClientId, or clientId when cliClientId is not set.
  • Instance Dex (Argo CD dex.config or Kargo's embedded Dex): nothing to register. Only loopback callbacks on localhost are accepted.
  • Local accounts: when the instance has no SSO configured, Argo CD local accounts and the Kargo admin account sign in through a form the portal serves. This is not a fallback for an instance whose SSO is configured but cannot serve MCP clients; such an instance stays unavailable.

See Argo CD MCP Access and Kargo MCP Access for the full behavior of each shape, and Argo CD SSO and Kargo SSO for how to configure the instance itself.

API keys​

An Akuity API key works on the platform endpoint with every sign-in shape. Authenticating with it needs no browser, no identity provider registration, and no outbound call from the portal server to an identity provider or a client metadata host. Tool calls still need the portal server to reach the instances and services they target. Send the key ID and secret as one bearer value:

Authorization: Bearer <API_KEY_ID>:<API_KEY_SECRET>

This applies to the platform endpoint only. A direct instance endpoint accepts the instance's native Argo CD or Kargo token instead. For a complete client configuration, use the Claude Code API-key example and replace its URL with https://<PORTAL_HOSTNAME>/mcp.

Outbound network access​

Interactive sign-in makes the portal server send these outbound requests. API key authentication does not send them.

  • Identity provider discovery and token endpoints, when the portal or an instance uses your own OIDC provider.
  • Auth0 token and revocation endpoints, when the portal uses Auth0.
  • Client metadata documents. Some clients identify themselves with a URL instead of a registered client ID, and the portal server fetches that document from the client's host. Claude Code uses this method with a document hosted on claude.ai. When the fetch is blocked, the sign-in is rejected with fetch_failed. In testing, Claude Code did not fall back to dynamic client registration, so the sign-in did not complete.

The bundled Dex is reached inside the cluster and needs no outbound access from the portal server.

For interactive sign-in, allow outbound HTTPS from the portal server to the required hosts. External OIDC discovery and token requests and client metadata fetches do not use HTTPS_PROXY; they need direct access. Auth0 token and revocation requests honor HTTP_PROXY, HTTPS_PROXY, and NO_PROXY. Configuring a proxy for Auth0 does not make client metadata fetches work through that proxy. If the required requests cannot reach their destinations, use API keys.

DNS proxies that return synthetic 198.18.0.0/15 addresses also trigger the SSRF guard. Exclude the identity provider and client metadata domains from that DNS mode so they resolve to their real public addresses; keep the SSRF checks enabled.

Agents that run on a user's machine, such as Claude Code, Codex, and Cursor, reach a private installation over the user's network. Hosted connectors, such as the Claude.ai and Claude Desktop custom connector, connect from the vendor's service and need the portal to be reachable from there.

Private certificate authorities​

When the portal, the instances, or the identity provider use certificates from a private CA:

  • MCP clients must trust that CA. For Claude Code, set NODE_EXTRA_CA_CERTS to a PEM file containing the CA before starting it, as described in its network configuration guide. Other clients have their own setting. Do not turn off certificate verification.
  • The portal server must trust the identity provider's CA for its own outbound calls. Portal login already requires this for your own OIDC provider, and the same trust covers MCP.
  • The portal server must also trust the CA of the instance ingress certificates. Platform tool calls that target an instance in another region go over that region's public HTTPS hostname. Those hostnames must be reachable from the primary region, and if the instance uses an IP allowlist, the primary's egress IPs must be allowed. See IP Allowlists.

The current chart has no extra-CA mount setting for portal-server. Use a ConfigMap and a Deployment patch to mount a bundle containing the image's public roots and your private CA. The example below uses namespace akuity-platform; replace it and <CONTEXT> with your values. Repeat it for each region's portal server that needs the CA.

  1. Save your private CA certificates as company-ca.pem. Copy the running image's public roots, combine them with your CA, and generate the ConfigMap manifest:

    kubectl --context <CONTEXT> -n akuity-platform exec deployment/portal-server -c portal-server -- \
    cat /etc/ssl/certs/ca-certificates.crt > public-ca.pem
    cat public-ca.pem company-ca.pem > ca-bundle.pem
    kubectl --context <CONTEXT> -n akuity-platform create configmap portal-ca-bundle \
    --from-file=ca-bundle.pem --dry-run=client -o yaml > portal-ca-bundle.yaml
  2. Save the following strategic merge patch as portal-ca-patch.yaml. It adds the mount without replacing the Deployment's existing volumes or environment variables:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: portal-server
    namespace: akuity-platform
    spec:
    template:
    spec:
    containers:
    - name: portal-server
    env:
    - name: SSL_CERT_FILE
    value: /etc/akuity-ca/ca-bundle.pem
    volumeMounts:
    - name: portal-ca-bundle
    mountPath: /etc/akuity-ca
    readOnly: true
    volumes:
    - name: portal-ca-bundle
    configMap:
    name: portal-ca-bundle
  3. Apply the ConfigMap and patch, then wait for the portal server to restart:

    kubectl --context <CONTEXT> apply -f portal-ca-bundle.yaml
    kubectl --context <CONTEXT> -n akuity-platform patch deployment portal-server \
    --type strategic --patch-file portal-ca-patch.yaml
    kubectl --context <CONTEXT> -n akuity-platform rollout status deployment/portal-server

Keep both manifests in your deployment configuration. Add the ConfigMap as a resource and the Deployment patch to your existing Kustomize overlay or Helm post-renderer so upgrades retain them. If you apply the patch manually, check and reapply it after each Helm upgrade. Rebuild the bundle from the new image's public roots when the platform image changes. After updating only the ConfigMap, restart portal-server so it reloads the CA bundle.

Verify the change by running a read against an instance in another region. If an external identity provider uses the CA, also complete a fresh sign-in. Setting SSL_CERT_FILE without mounting the bundle does not establish trust.

Check instance endpoints​

To check that a route exists, inspect the instance's MCP IngressRoute and connect an MCP client. A GET to /mcp without credentials returns 401 only when the route is published. Any other answer means the route is not there yet: Argo CD returns 404, and a Kargo hostname falls through to its normal ingress and returns the Kargo UI (200), or 405 for a POST. A 401 still does not prove that MCP works end to end. Follow Verify the connection, including an application or project read from an enabled instance in each region. Listing instances only reads platform records and does not check the connection to each instance. If users connect to direct instance endpoints, repeat the read through those connections too.

If MCP access is on for an instance but its route is still missing, for example because MCP access was turned on while a region still ran an older release, reconcile the instances once every region runs the new release. The command runs inside the portal server pod, re-applies all instances in one batch, and then waits for them to stay healthy:

kubectl -n akuity-platform exec deployment/portal-server -c portal-server -- \
akputil instance gradual-reconcile --percentage=100 --prune --skip-confirmation

--prune also removes routes that are no longer rendered. Replace akuity-platform with your installation's namespace.

Monitoring​

With monitoring enabled, the portal server ServiceMonitor scrapes the MCP metrics. portal_mcp_tool_calls_total counts every tool call on the platform and instance endpoints:

LabelValues
surfaceakp, argocd-instance, kargo-instance
toolThe tool name, limited to names in the tool catalog
domainargocd, kargo, kubernetes, agent, organization
actor_typeuser, apikey, serviceaccount, argocd, kargo, unknown
read_onlytrue, false
outcomesuccess, tool_error, rejected, oversized

Calls and reads by endpoint over the last seven days:

sum by (surface, read_only) (increase(portal_mcp_tool_calls_total[7d]))

The most used tools:

topk(10, sum by (tool) (increase(portal_mcp_tool_calls_total[7d])))

The counter has no organization label, because every organization would add a long-lived series. For per-organization activity, use the audit log, which tags changes made through MCP.

Two more counters help with sign-in problems: portal_mcp_external_issuer_failures_total counts identity provider discovery lookups that failed, and portal_mcp_cimd_rejections_total counts rejected client metadata documents, both by surface and reason.

Known limitations​

  • The current bundled-Dex chart points a non-primary region at a local Dex that is not installed there, which can prevent its portal server from starting. See Multi-region SSO.
  • Argo CD instances configured with a base path serve the direct endpoint only when they use dex.config. With oidc.config or local accounts, the direct endpoint is unavailable.
  • An identity provider whose hostname resolves to a non-public address works for portal login but not for MCP sign-in with your own OIDC provider. The MCP discovery and token requests block private, loopback, CGNAT (100.64.0.0/10), and benchmark (198.18.0.0/15) addresses. Fake-IP DNS proxies hand out addresses from the benchmark range. The bundled Dex is not affected.
  • The bundled Dex keeps tokens in memory. When its pod restarts, MCP refresh tokens stop working and each client signs in again.
  • portal.url with a path component is not supported for MCP.

Troubleshooting​

  • MCP Access is missing from Organization Settings: the portal runs a version earlier than v0.34.0. Check the running portal version.
  • A tool call fails with the MCP server is not enabled for this organization: the page is there but the platform endpoint is off. Turn it on under MCP Access → Endpoints and save. If the page is missing, see the previous item.
  • An instance's /mcp returns 404, or the Kargo UI or 405 on a Kargo instance: confirm MCP access is on for the instance, wait for reconciliation, and if the route is still missing, run the reconcile command in Check instance endpoints. Argo CD instances with a base path serve the direct endpoint only with dex.config.
  • Sign-in on the platform endpoint returns 404 or never starts: check the matching MCP client ID and callback, and confirm the issuer is HTTPS. The portal server logs a platform MCP line at startup for your own OIDC provider, for the bundled Dex on its own host, and for Auth0 when sso.auth0.mcpClientID is not set. The line names the resolved configuration or the missing setting. Auth0 with sso.auth0.mcpClientID set and the bundled Dex under /dex log no such line, because nothing needs registering. Look for portal MCP server enabled to confirm the endpoint is on.
  • The identity provider shows a redirect mismatch: the callback is not registered on the public client, or it was registered for a different hostname than agents use. The portal builds the callback from the hostname of the request, so portal.url and the registered URI must match that hostname exactly.
  • Claude Code sign-in is rejected or waits forever: check the portal server log for fetch_failed. If it appears, the portal server could not fetch the client metadata document: allow outbound HTTPS to the client's metadata host or use an API key. If it does not appear, work through the other sign-in items here and in the general troubleshooting page.
  • The portal server log shows dial blocked: destination is not a public unicast address: the SSRF guard refused a host that resolved to a non-public address, such as your identity provider or a client metadata host. Make that host resolve to its public address. If a fake-IP DNS proxy answers with 198.18.x.x addresses, see Outbound network access.
  • Certificate errors in the client or the portal server log: add the private CA to the client and to the portal server. See Private certificate authorities.
  • no_email during portal login: configure the identity provider to include an email in the ID token and check the user's directory email. For Entra, see Microsoft Entra ID (direct OIDC).
  • user not found after a successful identity provider login: sign in to the portal in a browser once, then reconnect the client.
  • A Kargo tool is listed but fails just after an upgrade or after MCP access is turned on: wait for instance initialization and reconciliation to finish. Healthy workloads, a present route, and a successful tools list do not prove that the instance's MCP service-account token is ready. If the error persists, record its reference and inspect both the portal server and platform controller logs. Validate recovery with an actual tool call.
  • Every client signed out at once: the bundled Dex restarted. Sign in again.

For sign-in, permission, and guardrail problems that are not specific to self-hosted installations, see MCP Troubleshooting.