Connect AI clients to Cardinal
Cardinal exposes every connected integration (Cardinal Data Lake, GitHub, Jira, Kubernetes, Slack, ServiceNow, …) as Model Context Protocol tools through Cardinal UI’s built-in MCP gateway. This page is the hub for wiring AI clients up: pick your client and follow its install guide, or use the manual configuration path at the bottom if a plugin isn’t an option in your environment.
Both deployment modes work the same way:
- Cardinal Cloud (SaaS) —
app.cardinalhq.io. The endpoint is already live; sign in with your Cardinal account and each plugin’sconnectflow does the rest. - Self-hosted Cardinal UI — your operator exposes Cardinal UI on your cluster’s Ingress. The plugins sign in through your browser like the rest of Cardinal UI; the manual configuration path uses a shared API key that your operator provisions (see Self-hosted setup below).
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.
Pick your client
Each per-session plugin registers a single cardinal MCP server (or, for Pi, native Cardinal tool discovery/execution) that exposes whichever integration tools your org has configured, and streams session activity to the Agent Outcomes dashboard. The per-session plugins (Claude, Codex, Cursor, Gemini, OpenCode, Pi) all support Cardinal Cloud and self-hosted, and each has a --telemetry-only variant if you want the dashboard without the tools.
| Client | Install guide | Notes |
|---|---|---|
| Claude Code | Install: Claude Code plugin | Managed via claude plugin marketplace; /cardinal:connect from inside Claude Code. |
| OpenAI Codex CLI | Install: Codex CLI plugin | Managed via codex plugin. |
| Cursor | Install: Cursor plugin | git clone + python3 scripts/cardinal-connect. Cloud agents need a --project install. |
| Google Gemini CLI | Install: Gemini CLI plugin | git clone + python3 scripts/cardinal-connect. Uses Gemini CLI’s native OTLP exporter for telemetry. |
| OpenCode | Install: OpenCode plugin | npm install from a GitHub release + cardinal-opencode connect. OpenCode 1.x only (V2 not supported). |
| Pi | Install: Pi extension | npm install + pi install + cardinal-pi connect. Tools via cardinal_list_tools / cardinal_call_tool, no separate MCP extension. |
| If your client isn’t on this list, use Manual MCP configuration at the bottom — any client that speaks streamable-HTTP MCP with a custom auth header can talk to the Cardinal UI gateway directly. |
Self-hosted: prepare your Cardinal UI instance
Skip this section if you’re on Cardinal Cloud — your endpoint is https://app.cardinalhq.io and there’s nothing to install on your side. On Cloud, each plugin’s browser consent flow mints per-user keys; there is no shared API key to manage.
For self-hosted Cardinal UI you provision one shared API key that gates the MCP endpoint for the manual configuration path. The plugins listed above sign users in through their browser and don’t need this key.
If your engineers will also use the Agent Outcomes dashboard, set MAESTRO_INGEST_ENDPOINT on the Cardinal UI deployment to your Cardinal Data Lake intake URL — the plugins’ connect flow reads the telemetry destination from Cardinal UI’s bundle, so this is what routes agent-session telemetry to your Cardinal Data Lake rather than omitting it. See Environment variables.
1. Provision the API key
Generate a strong random key (≥32 bytes of entropy) and store it under whatever secret-management scheme your cluster uses (Sealed Secrets, External Secrets Operator, plain kubectl create secret, etc.). Keep a copy in your team password manager — you cannot read it back out of Kubernetes after creation.
# Generate a 64-character URL-safe key
openssl rand -base64 48 | tr -d '\n=' | tr '/+' '_-'Plain kubectl example (use your real secret manager in production):
kubectl -n maestro create secret generic mcp-api-key \
--from-literal=MAESTRO_MCP_API_KEY="<paste-the-generated-key>"2. Wire MAESTRO_MCP_API_KEY onto the Cardinal UI container
Add the env var to your Helm values.yaml:
maestro:
env:
- name: MAESTRO_MCP_API_KEY
valueFrom:
secretKeyRef:
name: mcp-api-key
key: MAESTRO_MCP_API_KEYWhen MAESTRO_MCP_API_KEY is empty or unset, the system-API-key path is disabled and only the normal OIDC-authenticated routes work. The gateway sidecar itself runs unauthenticated on localhost regardless — auth is enforced at the Cardinal UI entry point.
helm upgrade and roll Cardinal UI. No new Service, no new Ingress, no chart-version requirement beyond a version that includes the system-API-key middleware (chart 0.8.7+ or the maestro v1.45.1+ image).
3. Lock it down
Because one API key gates the entire installation and the orgId is in the URL path, anyone with the key can hit every org. Treat it like an admin token:
- Prefer private DNS + a VPN/Tailscale entry point over an internet-facing hostname.
- If the endpoint must be reachable from the internet, put it behind an additional access layer (Cloudflare Access, an IP allowlist on your ingress controller, mTLS) — the API key alone is fine for an internal-only endpoint, not for the open internet.
4. Verify reachability
export CARDINAL_MCP_HOST="https://ui.example.internal"
export CARDINAL_MCP_API_KEY="<your-key>"
export CARDINAL_ORG_ID="<your-org-uuid>"
curl "$CARDINAL_MCP_HOST/api/health" # → "ok"
curl -H "X-CardinalHQ-API-Key: $CARDINAL_MCP_API_KEY" \
"$CARDINAL_MCP_HOST/api/orgs/$CARDINAL_ORG_ID/integrations" | jq '.integrations[].type'The second command should list every integration you have configured.
Manual MCP configuration without a plugin
Use this path only when the plugin listed above isn’t an option — a locked-down client install, a client we don’t yet have a plugin for, or scripted registration. Most users should use the plugin; it does this for you and keeps the configuration in sync as integrations change.
The manual path requires a shared API key, which means it applies to self-hosted Cardinal UI only — on Cardinal Cloud, auth is per-user via the browser consent flow, and there is no shared key to hand a client.
How requests reach the gateway
External MCP clients make HTTPS calls to Cardinal UI at:
https://<your-maestro-host>/api/orgs/<orgId>/integrations/<driver>/mcpCardinal UI authenticates the call and forwards it to its in-pod MCP gateway. Any Cardinal UI replica can serve any request — no sticky sessions, no special Ingress rules.
The endpoint path has three placeholders:
<orgId>is the UUID of the org whose integrations you want to talk to. Find it in Cardinal UI under Org settings → ID, or use the discovery endpoint below.<driver>is one oflakerunner,common,github,jira,slack,servicenow,kube, etc. The discovery endpoint enumerates what’s active for an org.- The host is whatever Cardinal UI listens on for your self-hosted install.
Authentication
External callers send X-CardinalHQ-API-Key: <your-key> on every request. Cardinal UI validates it against the MAESTRO_MCP_API_KEY env var on the Cardinal UI container; a match grants a service-account context with cross-org access. The header is also accepted as ?apiKey=… in the query string for clients that can’t set headers, though we strongly recommend the header form — query-string secrets leak into request logs.
Treat the key like an admin token: it gates every integration on every org in your installation. Rotate by re-issuing the secret value and doing kubectl -n maestro rollout restart deploy/<release>-maestro.
Discovery
Before configuring drivers, list the integrations active for your org:
export CARDINAL_MCP_HOST="https://ui.example.internal"
export CARDINAL_MCP_API_KEY="<your-key>"
export CARDINAL_ORG_ID="<your-org-uuid>"
curl -H "X-CardinalHQ-API-Key: $CARDINAL_MCP_API_KEY" \
"$CARDINAL_MCP_HOST/api/orgs/$CARDINAL_ORG_ID/integrations" | jq '.integrations[].type'Register per-driver MCPs in Claude Code
Claude Code accepts streamable-HTTP MCP servers via claude mcp add. Register each driver you want exposed:
# Logs / metrics / traces via Cardinal Data Lake
claude mcp add --transport http --scope user cardinal-lakerunner \
"$CARDINAL_MCP_HOST/api/orgs/$CARDINAL_ORG_ID/integrations/lakerunner/mcp" \
--header "X-CardinalHQ-API-Key: $CARDINAL_MCP_API_KEY"
# Built-in helpers (time resolution, chart rendering)
claude mcp add --transport http --scope user cardinal-common \
"$CARDINAL_MCP_HOST/api/orgs/$CARDINAL_ORG_ID/integrations/common/mcp" \
--header "X-CardinalHQ-API-Key: $CARDINAL_MCP_API_KEY"
# Code search / PR / issue lookup
claude mcp add --transport http --scope user cardinal-github \
"$CARDINAL_MCP_HOST/api/orgs/$CARDINAL_ORG_ID/integrations/github/mcp" \
--header "X-CardinalHQ-API-Key: $CARDINAL_MCP_API_KEY"--scope user puts the registration in ~/.claude.json (available across all your projects). Drop the flag to scope it to the current project instead.
Verify with claude mcp list — each server should show ✓ Connected. You may need to restart Claude Code for newly-registered MCPs to surface in an active session.
To remove a registration: claude mcp remove cardinal-lakerunner.
If you switch to the plugin later, /cardinal:connect automatically removes these cardinal-* entries from ~/.claude.json (a backup is written alongside) so they don’t collide with the plugin’s cardinal server.
Register in Codex CLI
Codex keeps its config in ~/.codex/config.toml and supports streamable-HTTP MCP servers with custom headers via the env_http_headers table.
Drop the CARDINAL_MCP_API_KEY export in your shell profile so every Codex session sees it, then append entries to ~/.codex/config.toml:
[mcp_servers.cardinal-lakerunner]
url = "https://ui.example.internal/api/orgs/<org-uuid>/integrations/lakerunner/mcp"
env_http_headers = { "X-CardinalHQ-API-Key" = "CARDINAL_MCP_API_KEY" }
[mcp_servers.cardinal-common]
url = "https://ui.example.internal/api/orgs/<org-uuid>/integrations/common/mcp"
env_http_headers = { "X-CardinalHQ-API-Key" = "CARDINAL_MCP_API_KEY" }
[mcp_servers.cardinal-github]
url = "https://ui.example.internal/api/orgs/<org-uuid>/integrations/github/mcp"
env_http_headers = { "X-CardinalHQ-API-Key" = "CARDINAL_MCP_API_KEY" }env_http_headers reads the header value from the named environment variable at request time, so the cleartext key never lands in the TOML file.
Verify with codex mcp list. To remove an entry: codex mcp remove cardinal-lakerunner.
Picking which drivers to expose
Discovery returns every active integration on the org. Most teams expose a small set:
| Driver | What it does | Useful when |
|---|---|---|
lakerunner | Logs, metrics, traces, service-graph queries | Day-to-day debugging, incident response |
common | Time resolution, chart rendering | Almost always — small, no creds needed |
github | Code search, file read, PR/issue lookup | Pairs well with lakerunner for “what changed?” |
jira | Issue search and read | Ops/incident workflows |
kube | Cluster-aware kubectl-style tools | When the AI needs to inspect a specific Kubernetes integration’s cluster |
slack | Read messages, post into channels | Status updates, escalation |
servicenow | Incident lookup | If ServiceNow is your ticketing system |
databricks, collector-editor, and other drivers appear in discovery when configured. Add what you’ll use; don’t add what you won’t (each connected server is loaded at client startup).
Troubleshooting
- Plugin says “already connected” — you have an existing connection. Re-run with
--rotateto issue fresh keys and replace it. The previous keys are valid for a short overlap so an active session keeps working. - Status shows everything connected but the
cardinaltools don’t appear — most clients only read their config when they start. Fully quit and reopen (Claude Code:Cmd-Qon macOS; Codex CLI, Cursor, Gemini CLI, OpenCode, and Pi: exit and relaunch). Unauthorized: missing API key(manual path) — header name is case-insensitive but the spelling matters:X-CardinalHQ-API-Key. Confirm independently withcurl "$CARDINAL_MCP_HOST/api/health"(no auth) returns 200 — if that fails, the endpoint isn’t reachable, not an auth problem.Forbiddenor empty discovery (manual path) — your<orgId>is wrong or the API key is for a different installation. Hit/api/orgs/<orgId>/integrationsdirectly withcurland inspect the body.Failed to connectinclaude mcp list— usually local DNS staleness right after a fresh hostname comes up. On macOS:sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder. Confirm independently withcurlfirst. Restarting the client also clears its in-process MCP connection state.kubetools return “missing cluster credentials” — thekubedriver expectsX-Kube-Clusterheaders that the Cardinal UI proxy populates on browser-driven calls. Driving it from a CLI without that header will only work for cluster-list operations; for full Kubernetes access against a specific cluster you’d need to setX-Kube-Cluster: <slug>yourself.- Tool calls succeed but return empty results — check that the underlying integration is enabled and
activein Cardinal UI (Org settings → Integrations).
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.