Problem
MCPExternalAuthConfig has nine spec.type values, but each consumer kind supports only a subset, and the unsupported combinations fail silently instead of being rejected or reported. A user who applies one of these configs sees a valid resource and a running workload, while the credential is never injected.
Confirmed against v0.40.1 and current main:
| Referencing resource |
headerInjection |
bearerToken |
MCPServer (externalAuthConfigRef) |
Silent no-op: explicit placeholder in addHeaderInjectionConfig (cmd/thv-operator/pkg/controllerutil/tokenexchange.go:386-401) |
Silently inert: wires runner.WithRemoteAuth, but remote auth only applies when RemoteURL is set (pkg/runner/runner.go:430-434), which MCPServer never sets |
MCPRemoteProxy (externalAuthConfigRef) |
Silent no-op: same dispatcher as above |
Works (the only functional consumer) |
VirtualMCPServer inline outgoingAuth |
Works |
Hard error: no converter registered (pkg/vmcp/auth/converters/interface.go:73-79) |
VirtualMCPServer outgoingAuth.source: discovered |
Works |
Backend silently dropped from the vMCP with only a log line (pkg/vmcp/workloads/k8s.go:290-296, 457-461) |
The discovered-mode case is a bad interaction: bearerToken on an MCPRemoteProxy is a legitimate, working configuration, but fronting that proxy with a discovered-mode vMCP makes the backend vanish from aggregation.
Beyond confusion, this is a security-adjacent footgun: someone who believes headerInjection is authenticating their proxy to a backend has a workload making unauthenticated calls while the resource reads as fully configured.
Ask
- Reconcile-time validation surfaced as status conditions. When a workload references an
MCPExternalAuthConfig whose type that kind doesn't support, set ExternalAuthConfigValidated=False with a reason like UnsupportedAuthType instead of no-op'ing. The mirroring plumbing already exists (this is the same flow that surfaces EnterpriseRequired today). For vMCP discovered mode, surface the skipped backend in VirtualMCPServer status rather than log-and-drop.
- Document supported consumer kinds in the CRD. State which kinds support each
spec.type in the field godoc on MCPExternalAuthConfigSpec.Type and on each consumer's externalAuthConfigRef, so it flows into the generated schema descriptions and reference docs.
An admission webhook rejecting the combination at apply time would be nice UX on top, but cross-resource checks at admission are best-effort (ref may not exist yet, either side can change later), so the conditions in (1) should be the source of truth.
Context
Found while reviewing stacklok/docs-website#1053, which initially documented headerInjection/bearerToken for MCPServer and MCPRemoteProxy per stacklok/docs-website#1013. Related fields with a similar accepted-but-unwired pattern: dcrConfig.softwareId/softwareStatement are plumbed to run config but never sent in the RFC 7591 registration request (pkg/auth/dcr/resolver.go:800-807).
Problem
MCPExternalAuthConfighas ninespec.typevalues, but each consumer kind supports only a subset, and the unsupported combinations fail silently instead of being rejected or reported. A user who applies one of these configs sees a valid resource and a running workload, while the credential is never injected.Confirmed against v0.40.1 and current
main:headerInjectionbearerTokenMCPServer(externalAuthConfigRef)addHeaderInjectionConfig(cmd/thv-operator/pkg/controllerutil/tokenexchange.go:386-401)runner.WithRemoteAuth, but remote auth only applies whenRemoteURLis set (pkg/runner/runner.go:430-434), whichMCPServernever setsMCPRemoteProxy(externalAuthConfigRef)VirtualMCPServerinlineoutgoingAuthpkg/vmcp/auth/converters/interface.go:73-79)VirtualMCPServeroutgoingAuth.source: discoveredpkg/vmcp/workloads/k8s.go:290-296,457-461)The discovered-mode case is a bad interaction:
bearerTokenon anMCPRemoteProxyis a legitimate, working configuration, but fronting that proxy with a discovered-mode vMCP makes the backend vanish from aggregation.Beyond confusion, this is a security-adjacent footgun: someone who believes
headerInjectionis authenticating their proxy to a backend has a workload making unauthenticated calls while the resource reads as fully configured.Ask
MCPExternalAuthConfigwhose type that kind doesn't support, setExternalAuthConfigValidated=Falsewith a reason likeUnsupportedAuthTypeinstead of no-op'ing. The mirroring plumbing already exists (this is the same flow that surfacesEnterpriseRequiredtoday). For vMCP discovered mode, surface the skipped backend inVirtualMCPServerstatus rather than log-and-drop.spec.typein the field godoc onMCPExternalAuthConfigSpec.Typeand on each consumer'sexternalAuthConfigRef, so it flows into the generated schema descriptions and reference docs.An admission webhook rejecting the combination at apply time would be nice UX on top, but cross-resource checks at admission are best-effort (ref may not exist yet, either side can change later), so the conditions in (1) should be the source of truth.
Context
Found while reviewing stacklok/docs-website#1053, which initially documented
headerInjection/bearerTokenforMCPServerandMCPRemoteProxyper stacklok/docs-website#1013. Related fields with a similar accepted-but-unwired pattern:dcrConfig.softwareId/softwareStatementare plumbed to run config but never sent in the RFC 7591 registration request (pkg/auth/dcr/resolver.go:800-807).