Configure the Connector Gateway
The Connector Gateway provides an identity-aware MCP endpoint and brokers upstream OAuth for connectors. Enable it through the Stacklok Enterprise platform chart. Enabling it takes more than a single flag, because the gateway signs users in and stores their connector credentials itself.
This page uses the stacklok-enterprise release name and the stacklok-system
namespace from Deploy the platform. Several values below
embed those names, so adjust them if yours differ.
Prerequisites
- Stacklok Enterprise platform chart v0.22.0 or later. Earlier charts don't
pass the
global.redissettings to the gateway. - A corporate identity provider at
global.stacklok.primaryIdp. The gateway resolves every caller to a directory user before it decides what they can see. See Configure identity. - The Enterprise Manager, with
enterprise-manager.directory.enabled: true. The gateway gets each caller's identity, connector grants, and connector configuration from it. It needs its PostgreSQL database, set up as described in the Enterprise Manager database. To administer connectors across users in the console, also create theBYPASSRLSadmin database role described there. - The Enterprise Manager's key-encryption key, gRPC TLS, and NetworkPolicy,
configured as described in
Encrypting stored credentials.
The Enterprise Manager encrypts connector credentials and the identity
provider's client secret with this key, and returns them to the gateway only
over TLS. That section's NetworkPolicy example already admits pods labeled
app.kubernetes.io/name: connector-gateway. - Redis or Valkey, reachable from the cluster. The embedded authorization
server stores its sessions only in Redis, at every replica count, and the
chart doesn't deploy an instance. Configure it once under
global.redis, as described in Global Redis/Valkey defaults, and the gateway inherits the host, port, username, password Secret, and TLS setting. To connect as a least-privilege ACL user, see Restrict the Redis user.
Prepare the gateway's secrets
The gateway needs three Secrets in the release namespace. Create them before you install or upgrade.
Key-encryption key (KEK). The gateway seals the upstream OAuth tokens and
client registrations it stores with this key. It is separate from the Enterprise
Manager's KEK. The Secret holds 32 random bytes under the key kek:
openssl rand 32 > kek
kubectl create secret generic connector-gateway-kek \
--namespace stacklok-system \
--from-file=kek=./kek
Keep a backup of the key. Losing or replacing it makes every credential the gateway has stored unreadable. To change the key later, add a new version rather than replacing it, as described in Rotate the Connector Gateway encryption key.
Authorization server keys. The signing key keeps issued tokens valid across restarts and replicas, and the HMAC secret keeps authorization codes and refresh tokens valid. The signing key must be a PEM-encoded ECDSA private key:
openssl ecparam -name prime256v1 -genkey -noout -out signing-key.pem
openssl rand 32 > hmac-secret
kubectl create secret generic connector-gateway-auth-server-keys \
--namespace stacklok-system \
--from-file=signing-key.pem=./signing-key.pem \
--from-file=hmac-secret=./hmac-secret
kek.generate: true and authServerKeys.generate: true have the chart create
and preserve the key material itself, but only on a live helm install or
helm upgrade. helm template, Argo CD, and other render-and-apply workflows
can't look up the existing Secret, so they would mint new keys on every sync and
orphan everything sealed or signed with the old ones. Use existingSecret for
those workflows.
Enterprise Manager CA bundle. The gateway verifies the Enterprise Manager's
gRPC serving certificate against a CA bundle. Put only the CA certificate in
this Secret. If cert-manager issues the Enterprise Manager's certificate, copy
ca.crt out of its Secret rather than referencing that Secret, which also holds
the private key:
kubectl create secret generic enterprise-manager-grpc-ca \
--namespace stacklok-system \
--from-file=ca.crt=./ca.crt
A copied Secret goes stale when cert-manager rotates the CA. For GitOps, keep
the copy in sync automatically instead, for example with a trust-manager
Bundle that targets a Secret, or issue the Enterprise Manager's certificate
from a cert-manager CA Issuer whose CA you manage and distribute yourself.
Enable the Connector Gateway
Add the following to the values file you use for the platform. Merge the
global keys into your existing global block rather than adding a second one.
global:
stacklok:
# primaryIdp from "Configure identity" stays here unchanged.
connectorGateway:
enabled: true
connectorGatewayId: '<GATEWAY_ID>'
authServerIssuer: 'https://<GATEWAY_HOST>'
# Redis or Valkey from "Global Redis/Valkey defaults"
redis:
host: 'redis.example.com'
port: 6379
existingSecret: 'redis-auth'
connector-gateway:
vmcpConfig:
groupRef: 'connector-gateway'
incomingAuth:
type: oidc
oidc:
issuer: 'https://<GATEWAY_HOST>'
audience: 'https://<GATEWAY_HOST>/gw/mcp'
enterpriseConfig:
authServer:
schema_version: 'v0.1.0'
allowed_audiences:
- 'https://<GATEWAY_HOST>/gw/mcp'
baseline_client_scopes:
- openid
- offline_access
storage:
type: redis
controlPlane:
incomingAuth:
oidc:
issuer: '<PRIMARY_IDP_ISSUER>' # same as global.stacklok.primaryIdp.issuer
audience: '<CONSOLE_TOKEN_AUDIENCE>'
directory:
addr: 'stacklok-enterprise-enterprise-manager.stacklok-system.svc.cluster.local:9443'
directoryTLS:
enabled: true
caBundle:
secretName: 'enterprise-manager-grpc-ca'
kek:
existingSecret: 'connector-gateway-kek'
authServerKeys:
existingSecret: 'connector-gateway-auth-server-keys'
| Value | Purpose |
|---|---|
global.stacklok.connectorGateway.enabled | Deploy the Connector Gateway |
global.stacklok.connectorGatewayId | Identify this gateway to the Enterprise Manager and console |
global.stacklok.authServerIssuer | Set the gateway's public authorization server URL |
vmcpConfig.groupRef | Name the gateway in its status and logs. Required; any non-empty value works |
vmcpConfig.incomingAuth | Require tokens from the gateway's own authorization server on the MCP endpoint |
enterpriseConfig.authServer | Configure the embedded authorization server and its Redis storage |
enterpriseConfig.controlPlane | Accept the corporate tokens the console sends to the gateway's REST API |
enterpriseConfig.directory.addr | Point the gateway at the Enterprise Manager's TLS gRPC port |
directoryTLS | Mount the Enterprise Manager CA bundle and the gateway's ServiceAccount token |
kek, authServerKeys | Reference the Secrets from Prepare the gateway's secrets |
Set vmcpConfig.groupRef to any non-empty name; the gateway refuses to start
while it's empty. The gateway reads its backends from the Enterprise Manager, so
no MCPGroup by that name needs to exist.
Set baseline_client_scopes to openid and offline_access so MCP clients
that register through dynamic client registration, such as Claude Code, Cursor,
and VS Code, can obtain refresh tokens.
Choose the gateway ID and issuer
Choose a stable connectorGatewayId such as prod-eu or platform-staging,
and give every install its own value. The gateway registers this ID with the
Enterprise Manager, so two installs sharing one ID act as a single gateway. The
value cannot be empty or contain a colon. Changing it creates a new gateway
identity and changes the prefix used for stored per-user tokens.
Set authServerIssuer to the HTTPS URL that clients use to reach this
deployment. The gateway derives connector OAuth callbacks as
{issuer}/oauth/callback. Configure the value under global.stacklok; the
chart rejects equivalent settings under the component's internal configuration.
The MCP endpoint is {issuer}/gw/mcp, and that URL is the token audience. Set
vmcpConfig.incomingAuth.oidc.issuer to the same value as authServerIssuer,
and list the audience in allowed_audiences exactly as it appears in
incomingAuth.oidc.audience. The gateway compares these strings exactly and
refuses to start when they disagree.
Omit upstreams from enterpriseConfig.authServer. The authorization server
loads its identity providers from the Enterprise Manager: your corporate
identity provider from global.stacklok.primaryIdp, plus the connector identity
providers that admins register in the Enterprise Manager.
Accept console tokens on the REST API
The console calls the gateway's REST API with the user's corporate identity
provider access token. enterpriseConfig.controlPlane.incomingAuth.oidc
configures the validator that accepts those tokens.
Set issuer to global.stacklok.primaryIdp.issuer; the gateway refuses to
start if it doesn't match an identity provider it signs users in with. Set
audience to the exact aud value of the access tokens the console sends. The
console sends the access token from its own sign-in, so this is the audience
your identity provider assigns to tokens for the console's OAuth client, such as
cloud-ui in the Okta example in
Configure platform identity. To confirm it, decode
an access token issued to that client as shown in
Verify a token. Prefer an audience
that only Stacklok components accept over a shared default such as Okta's
api://default, which would let tokens minted for unrelated applications reach
the gateway. The token must be a JWT, so use an authorization server that issues
JWT access tokens rather than Okta's org authorization server. To fetch signing
keys from a URL other than the issuer's discovery document, also set jwksUrl.
Identify users by a claim other than sub
By default, the gateway and the Enterprise Manager identify users by the sub
claim in your corporate identity provider's tokens. If your provider identifies
users with a different claim, such as uid in Okta, configure it on both sides.
Set global.stacklok.primaryIdp.subjectClaim, which the gateway uses at
sign-in, and add an enterprise-manager.directory.issuers entry whose
issuer_url matches global.stacklok.primaryIdp.issuer exactly, with the same
claim in binding_claim:
global:
stacklok:
primaryIdp:
# issuer and client settings from "Configure identity" stay here.
subjectClaim: 'uid'
enterprise-manager:
directory:
issuers:
- id: 'corporate-idp'
issuer_url: 'https://idp.example.com'
audience: 'enterprise-manager'
binding_claim: 'uid'
The gateway reads the claim from the ID token, and it must be a top-level
string claim that also appears on refreshed ID tokens. Okta adds uid to access
tokens from a custom authorization server, but not to ID tokens. In Okta, add a
uid claim with the value user.id and the token type ID Token. Choose the
claim before users sign in, because it becomes each user's stable identity in
the directory.
The console's access tokens must carry the same top-level claim, because the
REST API resolves users from them. Okta's custom authorization servers add uid
to access tokens by default.
The same issuers entry configures SCIM provisioning for that provider. See
SCIM identity providers.
Connect the gateway to the Enterprise Manager
enterpriseConfig.directory.addr requires exactly one transport. With
directoryTLS.enabled: true, the gateway dials the Enterprise Manager's TLS
gRPC port (9443) and authenticates with a projected ServiceAccount token. The
Enterprise Manager side of that connection needs a TLS listener, caller
authentication, and the gateway's ServiceAccount on three allowlists:
enterprise-manager:
grpc:
tls:
enabled: true
existingSecret: 'enterprise-manager-grpc-tls'
callerAuth:
clusterOidcIssuer: '<CLUSTER_OIDC_ISSUER>'
saSubjectAllowlist:
- 'system:serviceaccount:stacklok-system:stacklok-enterprise-connector-gateway'
gatewaySaSubjectAllowlist:
- 'system:serviceaccount:stacklok-system:stacklok-enterprise-connector-gateway'
credentialSaSubjectAllowlist:
- 'system:serviceaccount:stacklok-system:stacklok-enterprise-connector-gateway'
grpc.tls is the same setting you configured in
Encrypting stored credentials.
The chart refuses to render a TLS gateway transport while
enterprise-manager.grpc.tls.enabled is false.
Set clusterOidcIssuer to your cluster's ServiceAccount token issuer, which
this command prints:
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer
The Enterprise Manager fetches the issuer's discovery document and signing keys directly from that URL, not through the Kubernetes API server. On managed clusters such as Amazon EKS, the issuer is a public URL, so allow egress to it from the Enterprise Manager pods.
Each allowlist covers different calls from the gateway. saSubjectAllowlist
covers identity resolution and per-user connector configuration.
gatewaySaSubjectAllowlist covers the gateway's startup registration and its
catalog reads. credentialSaSubjectAllowlist covers credential resolution,
which the gateway needs for the identity provider's client secret. The subject
is system:serviceaccount:<NAMESPACE>:<RELEASE_NAME>-connector-gateway. The
chart fills in a fourth list, meteringSaSubjectAllowlist, from the same
subject for
tool call recording. If you
change the gateway's name, update these values as described in
Override the gateway's name. For a development
cluster without TLS, see
Use a cleartext connection for development.
Keep the AI Gateway's access
Caller authentication covers both the TLS and cleartext gRPC ports, so turning it on also gates the AI Gateway. Its API key service checks every virtual API key against the Enterprise Manager. If your platform also runs the AI Gateway with virtual API keys:
- Create the
AIGatewayresource in the release namespace. The Enterprise Manager's directory NetworkPolicy admits the API key service only from that namespace. - Add the API key service's subject to
saSubjectAllowlist:system:serviceaccount:stacklok-system:<AIGATEWAY_NAME>-api-key-service, where<AIGATEWAY_NAME>is the name of yourAIGatewayresource. - Keep
spec.auth.virtualAPIKeys.directory.insecureon theAIGatewayat its default,false, so the API key service presents its ServiceAccount token. - Unset
enterprise-manager.grpc.insecureif you set it for the AI Gateway. It skips caller authentication, and the Enterprise Manager refuses to start with it alongside a directory key-encryption key.
The chart refuses to render while the AI Gateway is enabled and no subject
ending in -api-key-service is on the list, even if you don't use virtual API
keys. In that case, set
enterprise-manager.grpc.callerAuth.acknowledgeNoApiKeyServiceCaller: true
instead of adding a subject.
If you set global.stacklok.directoryGrpcEndpoint to add group labels to AI
Gateway telemetry, also add
system:serviceaccount:stacklok-system:<AIGATEWAY_NAME>-main-processor to
saSubjectAllowlist.
Corporate identity provider settings
The gateway reads these optional keys from global.stacklok.primaryIdp, in
addition to the issuer and client settings in
Configure platform identity:
| Key | Purpose |
|---|---|
clientClaim | Access-token claim that names the OAuth client. Empty reads cid or client_id. Allowed values are cid, client_id, azp, and appid; set azp for Dex, or azp or appid for Microsoft Entra ID |
subjectClaim | Claim that identifies each user, read from ID tokens at sign-in and from the console's access tokens. Defaults to sub. Must match the Enterprise Manager's binding_claim; see Identify users by a claim other than sub |
clientClaimAllowUnsafe | Boolean. Accepts a clientClaim outside the allowed values. A weaker claim can match tokens minted for a different client, and the gateway logs a warning at startup |
allowPrivateIps | Boolean. Lets discovery and token exchange reach an identity provider on a private or link-local address. HTTPS is still required for non-localhost hosts |
caBundleRef | ConfigMap or Secret holding the PEM CA bundle for an identity provider served by a private CA. Set exactly one of configMapRef or secretRef, each with a name, plus an optional key (default ca.crt) |
Set the boolean keys as unquoted true or false. The chart refuses a quoted
string, and --set-string produces one.
caBundleRefFor sign-in, the gateway trusts caBundleRef in addition to the system roots.
Its control-plane token validator, which checks the corporate tokens the console
sends, trusts only this bundle. Include every intermediate and root certificate
in the chain that serves your identity provider's JWKS endpoint. A bundle that
omits a public intermediate or root makes every control-plane request fail with
401, even though sign-in works.
The gateway reads the bundle once at startup. After you replace the contents of
the same ConfigMap or Secret, restart the gateway with
kubectl rollout restart deployment/stacklok-enterprise-connector-gateway -n stacklok-system.
Publish and verify the endpoints
Publish the gateway at the HTTPS origin you set in authServerIssuer. The chart
creates two HTTP Services in the release namespace:
| Traffic | Default Service | Port |
|---|---|---|
| MCP calls and authorization server | <RELEASE_NAME>-connector-gateway | 8443 |
| Per-user REST API | <RELEASE_NAME>-connector-gateway-control-plane | 8090 |
Confirm the installed names and ports, especially if you use Helm name or port overrides:
kubectl get svc -n stacklok-system
kubectl get svc stacklok-enterprise-connector-gateway -n stacklok-system -o yaml
Route the public hostname
The following Gateway API example requires an existing Gateway with an HTTPS
listener named <HTTPS_LISTENER>. Its certificate must cover <GATEWAY_HOST>,
and its allowedRoutes must admit routes from stacklok-system. Use the
Gateway and namespace managed by your ingress team. The controller must be able
to reach the backend Service over HTTP, including through any NetworkPolicy.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: connector-gateway
namespace: stacklok-system
spec:
parentRefs:
- name: <EDGE_GATEWAY_NAME>
namespace: <EDGE_GATEWAY_NAMESPACE>
sectionName: <HTTPS_LISTENER>
hostnames:
- '<GATEWAY_HOST>'
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: stacklok-enterprise-connector-gateway
port: 8443
Replace the placeholders and adjust the backend name and port to match your Service. Apply the route, then inspect its parent conditions:
kubectl apply -f connector-gateway-route.yaml
kubectl get httproute connector-gateway -n stacklok-system \
-o jsonpath='{.status.parents}' | jq .
kubectl get gateway <EDGE_GATEWAY_NAME> -n <EDGE_GATEWAY_NAMESPACE> -o yaml
Wait for Accepted and ResolvedRefs to be True on the route and for the
Gateway to be Programmed. Point <GATEWAY_HOST> in DNS at your edge Gateway's
address. Terminate TLS there and preserve request paths and the Authorization
header. Routing / preserves /gw/mcp, /.well-known/*, and /oauth/*,
including the callback at /oauth/callback. Register that callback URL on the
corporate identity provider's OAuth client.
Configure your controller to pass streamed responses as they arrive and keep long-lived MCP connections open. Set its request and idle timeouts to accommodate your tool calls, and disable response buffering if your controller buffers by default.
Keep the REST API reachable by the console
The console calls the control-plane Service from inside the cluster, so that
Service can remain cluster-internal. For external API clients, create a separate
HTTPS route to stacklok-enterprise-connector-gateway-control-plane on port
8090. You can adapt the HTTPRoute above with a separate hostname and backend;
preserve the /v1 paths. See the
Connector Gateway API reference for
authentication and a port-forward alternative.
Verify each part of the connection
-
Confirm the gateway is ready and registered. Check its pod and find your
connectorGatewayIdin the console's administration experience under Connectors:kubectl rollout status deployment/stacklok-enterprise-connector-gateway \-n stacklok-system -
Check DNS, TLS, and authorization-server routing from a client machine:
curl --fail --silent --show-error \https://<GATEWAY_HOST>/.well-known/openid-configuration | jq .The response's
issuermust matchauthServerIssuer, and its authorization and token endpoints must use the published gateway origin. For a private CA, use--cacert <CA_FILE>and install that CA in your clients' trust stores. -
Check the REST API independently. Use the API reference's port-forward and a corporate identity provider access token accepted by the gateway's control-plane validator:
curl --fail --silent --show-error \http://localhost:8080/v1/users/me/gateway/connectors/status \-H 'Authorization: Bearer <CORPORATE_ACCESS_TOKEN>' | jq .A successful JSON response confirms control-plane authentication and directory access. It can be empty before you grant connectors. This API uses corporate tokens; the MCP endpoint uses tokens from the gateway's own authorization server.
-
Complete the Connector Gateway quickstart with a test account to verify browser sign-in and an authenticated MCP tool call through
https://<GATEWAY_HOST>/gw/mcp.
For discovered Kubernetes backends, also complete Connect to in-cluster endpoints before testing their tools. Once these checks pass, follow Roll out gateway clients.
How connector access works
The gateway asks the Enterprise Manager to resolve each caller and return their connector grants. Each connector's connector policy determines which callers the gateway exposes it to.
Turn on tool call recording and observability
Tool call recording, which populates the console's Tool Usage screen, is off
by default. Turn it on with connector-gateway.enterpriseConfig.outbox.enabled
and connector-gateway.meteringTokenProjection.enabled, as described in
Record tool calls.
Audit events, OpenTelemetry export, and Prometheus metrics are also off by default. See Forward Connector Gateway audit logs and Collect Connector Gateway telemetry.
After enabling
- Confirm the gateway pod is ready. It registers with the Enterprise Manager at startup and becomes ready once its first connector catalog read succeeds. The admin Connectors view in the console then appears for the ID you set.
- Add connectors and grant them to groups, either in the console under
Connectors or through the
Enterprise Manager API, which exposes the
same operations under
/v1/gateways/{gateway_id}/connectors. - Direct users to the deployment-specific instructions in the console. See Roll out gateway clients.
Advanced configuration
These settings apply only to some installs. Configure them alongside the values above.
Restrict the Redis user
If the Connector Gateway is the only component that uses global.redis, you can
connect as a least-privilege ACL user. Set global.redis.username to that user,
and apply this reference ACL to your Redis or Valkey instance with your own
password. global.redis.username applies to every component that inherits
global.redis, so don't restrict the user this way while other components share
it.
user default reset off
user cgw reset on ><REDIS_PASSWORD> resetchannels ~thv:auth:* ~connector-gateway:* +@read +@write +@connection +@transaction -@admin -@dangerous -@scripting -script -function -fcall -fcall_ro +eval +evalsha -config -acl -flushall -flushdb -swapdb -replicaof -slaveof -cluster -failover -save -bgsave -bgrewriteaof -debug -shutdown -monitor -client|tracking -keys -randomkey -dbsize -scan
The thv:auth:* keys hold authorization server sessions, and the
connector-gateway:* keys hold the tool call recording queue, a check for the
gateway's encryption key, and client registration locks. The first line disables
the built-in default user, which otherwise grants full access to any
connection that falls back to it.
Use a cleartext connection for development
For a development cluster without TLS, set
connector-gateway.enterpriseConfig.directory.insecure: true in place of
directoryTLS, point addr at the cleartext port (9091), and set
enterprise-manager.grpc.insecure: true. The chart requires both settings
together. This transport carries credentials in cleartext with no caller
authentication, so reserve it for local testing. The Enterprise Manager refuses
to start with grpc.insecure: true while directory.kek is configured, so this
transport works only on an install without the key-encryption key.
Override the gateway's name
The umbrella chart computes several Enterprise Manager and console values from
the gateway's default name, <RELEASE_NAME>-connector-gateway. If you set
connector-gateway.fullnameOverride, nameOverride, serviceAccount.name, or
serviceAccount.create: false, or change the control-plane Service, update
these values to match:
| Setting | Replacement value |
|---|---|
enterprise-manager.grpc.callerAuth.saSubjectAllowlist, gatewaySaSubjectAllowlist, credentialSaSubjectAllowlist, and meteringSaSubjectAllowlist | system:serviceaccount:<NAMESPACE>:<SERVICE_ACCOUNT_NAME> |
enterprise-manager.grpc.callerAuth.httpSaSubjectAllowlist, only when global.stacklok.enterpriseManager.url is set | The same subject, alongside any AI Gateway entry already on the list |
toolhive-cloud-ui.connectorGatewayUrl, which enables the console's Connectors pages | http://<CONTROL_PLANE_SERVICE>.<NAMESPACE>:<CONTROL_PLANE_PORT> |
enterprise-manager.grpc.networkPolicy.allowedClientSelector, only with nameOverride | app.kubernetes.io/name: <NAME_OVERRIDE> |
To read the names, render the chart with helm template and find the gateway's
ServiceAccount and its -control-plane Service. With fullnameOverride, the
ServiceAccount and Services take the override as their name; with
nameOverride, they become <RELEASE_NAME>-<NAME_OVERRIDE>.
nameOverride also changes the pods' app.kubernetes.io/name label. The
Enterprise Manager's directory NetworkPolicy admits only pods labeled
connector-gateway, so renamed pods reach the gRPC ports only through
grpc.networkPolicy. If you don't enable that policy, use fullnameOverride,
which keeps the label.
Next steps
- Connector Gateway quickstart to register a first connector and test the end-to-end flow.
- Connectors in the console to register connectors and grant access.
- Identity and directory for the users and groups that access is granted to.
Troubleshooting
The gateway pod stays unready or restarts
The readiness probe fails until the gateway's first connector catalog read from the Enterprise Manager succeeds, so an unready pod means the gateway can't complete its calls to the Enterprise Manager. Check the pod's logs:
kubectl logs deployment/stacklok-enterprise-connector-gateway -n stacklok-system
PermissionDeniedduring registration, followed by a restart. The gateway's ServiceAccount subject is missing fromenterprise-manager.grpc.callerAuth.gatewaySaSubjectAllowlist. Add it as shown in Connect the gateway to the Enterprise Manager.Unauthenticatedon every call to the Enterprise Manager. The Enterprise Manager rejected the gateway's ServiceAccount token. Check thatclusterOidcIssuermatches the issuer your cluster reports.- TLS handshake errors. Check that
enterprise-manager-grpc-caholds the CA that signed the Enterprise Manager's gRPC certificate, and thataddruses port9443and a hostname on that certificate. After you rotate the CA, restart the gateway; it reads the bundle only at startup. - Connection timeouts. Check that the Enterprise Manager's gRPC
NetworkPolicy admits the gateway pods, and that the gateway can reach the
Redis or Valkey host. After a
nameOverride, see Override the gateway's name.
The Enterprise Manager fails to start or rejects every gRPC caller
The Enterprise Manager fetches the clusterOidcIssuer discovery document at
startup. If it can't, it exits with cluster OIDC bootstrap failed. If it
starts but later can't refresh the issuer's signing keys, it rejects every gRPC
caller, including the gateway. In both cases, check that clusterOidcIssuer
matches your cluster's issuer and that the Enterprise Manager pods can reach it.
The console's Connectors pages return 401
The gateway's REST API rejected the console's access token. Check these causes:
enterpriseConfig.controlPlane.incomingAuth.oidcis unset. Without it, the REST API accepts only tokens from the gateway's own authorization server.audiencedoesn't match the console token'saudclaim, or the token is opaque rather than a JWT. See Accept console tokens on the REST API.- With a
subjectClaimother thansub, the console's access token lacks that claim, orsubjectClaimand the Enterprise Manager'sbinding_claimdiffer. When they differ, users can't be resolved and sign-in through MCP clients fails too. See Identify users by a claim other thansub.
MCP clients stay at "needs authentication"
If the authorization server rejects the client's sign-in with invalid_scope,
the client registered with openid and then requested offline_access. Set
enterpriseConfig.authServer.baseline_client_scopes to openid and
offline_access as shown in
Enable the Connector Gateway.
AI Gateway requests fail or lose group labels
- Every virtual-key request fails with
503andkey store temporarily unavailable. The Enterprise Manager refused the API key service. Check its subject onsaSubjectAllowlist, its namespace, and itsdirectory.insecuresetting, as described in Keep the AI Gateway's access. - AI Gateway telemetry has no group labels, and no error is logged. The main
processor's subject is missing from
saSubjectAllowlist.
The render fails or the gateway is refused after a name override
The umbrella chart checks meteringSaSubjectAllowlist and
toolhive-cloud-ui.connectorGatewayUrl against the gateway's actual names and
fails the render when they differ. The error message prints the value to set. No
check covers httpSaSubjectAllowlist, so a stale entry there shows up only as
refused HTTP calls from the gateway to the Enterprise Manager. See
Override the gateway's name.