Connect AI clients to the MCP gateway
Cardinal Maestro ships with a built-in MCP gateway that exposes every connected integration (Lakerunner, GitHub, Jira, Kubernetes, Slack, and so on) as Model Context Protocol tools. The gateway runs as a sidecar inside every Maestro pod; external clients reach it through Maestro’s HTTP server, not the sidecar directly. This page covers wiring Claude Code and OpenAI Codex CLI up to your Maestro instance.
Two deployment modes are covered, in the order most readers will encounter them:
- In your VPC (self-hosted) — the common case. Maestro is already exposed inside your cluster; you add a shared API key and point your AI clients at it.
- Cardinal Cloud (SaaS) — for customers on
app.cardinalhq.io. Cardinal hosts the endpoint and your admin shares an API key.
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.
How requests reach the gateway
External MCP clients make HTTPS calls to Maestro at:
https://<your-maestro-host>/api/orgs/<orgId>/integrations/<driver>/mcpMaestro authenticates the call (see below), then forwards it over localhost to its in-pod gateway sidecar. The gateway runs in stateless mode, so any Maestro replica can serve any request — there are no sticky-session concerns, no Mcp-Session-Id continuity requirements, and no special routing rules in your Ingress.
The endpoint path has three placeholders:
<orgId>is the UUID of the org whose integrations you want to talk to. Find it in Maestro 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 Maestro listens on —
app.cardinalhq.iofor Cardinal Cloud, or whatever Ingress hostname your operator chose for the self-hosted install.
Authentication
External callers send X-CardinalHQ-API-Key: <your-key> on every request. Maestro validates it against the MAESTRO_MCP_API_KEY env var on the Maestro 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 clients, list the integrations active for your org:
export CARDINAL_MCP_HOST="https://app.cardinalhq.io"
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'Health check (no auth required):
curl "$CARDINAL_MCP_HOST/api/health"In your VPC (self-hosted)
This is the path for any customer running Maestro inside their own Kubernetes cluster. You already have a hostname pointing at Maestro (your existing Ingress for the web UI); we add the API-key env var and you’re done.
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 Maestro 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 Maestro entry point.
helm upgrade and roll Maestro. 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
export CARDINAL_MCP_HOST="https://maestro.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.
Cardinal Cloud (SaaS)
If you use Cardinal Cloud at app.cardinalhq.io, the MCP entry point is already live. You don’t stand up any infrastructure. From your Cardinal admin contact you’ll need:
- The API key (provisioned per team; shared via your password manager).
- Your org UUID (visible in Maestro under Org settings, or returned from the discovery endpoint).
export CARDINAL_MCP_HOST="https://app.cardinalhq.io"
export CARDINAL_MCP_API_KEY="<from-your-admin>"
export CARDINAL_ORG_ID="<your-org-uuid>"
curl "$CARDINAL_MCP_HOST/api/health"
curl -H "X-CardinalHQ-API-Key: $CARDINAL_MCP_API_KEY" \
"$CARDINAL_MCP_HOST/api/orgs/$CARDINAL_ORG_ID/integrations" | jq '.integrations[].type'Everything below works identically whether CARDINAL_MCP_HOST is https://app.cardinalhq.io or your in-VPC hostname.
Configure Claude Code
Claude Code accepts streamable-HTTP MCP servers via claude mcp add. Register each integration you want to expose:
# Logs / metrics / traces via Lakerunner
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. Tools from each driver are then available in your Claude Code session under the configured names. 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.
Configure Codex CLI
OpenAI Codex CLI keeps its config in ~/.codex/config.toml. It supports streamable-HTTP MCP servers with custom headers via the env_http_headers table.
Export the key once (drop it in your shell profile so every Codex session sees it):
export CARDINAL_MCP_API_KEY="<your-key>"Then append entries to ~/.codex/config.toml:
[mcp_servers.cardinal-lakerunner]
url = "https://app.cardinalhq.io/api/orgs/<org-uuid>/integrations/lakerunner/mcp"
env_http_headers = { "X-CardinalHQ-API-Key" = "CARDINAL_MCP_API_KEY" }
[mcp_servers.cardinal-common]
url = "https://app.cardinalhq.io/api/orgs/<org-uuid>/integrations/common/mcp"
env_http_headers = { "X-CardinalHQ-API-Key" = "CARDINAL_MCP_API_KEY" }
[mcp_servers.cardinal-github]
url = "https://app.cardinalhq.io/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 wire up
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
Unauthorized: missing API key— 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 — 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 Claude Code also clears its in-process MCP connection state.kubetools return “missing cluster credentials” — thekubedriver expectsX-Kube-Clusterheaders that the Maestro UI proxy populates on browser-driven calls. Driving it from Claude/Codex 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 Maestro (Org settings → Integrations).
Reach out to support@cardinalhq.io for support or to ask questions not answered in our documentation.