Skip to content

Operator: externalAuthConfigRef (headerInjection) on an MCPServerEntry is put into the vMCP config but its secret is not mounted when outgoingAuth.source: inline → vMCP fails to start #6837

Open

Description

@nilsberberich

Summary

A VirtualMCPServer with spec.outgoingAuth.source: inline and a backend MCPServerEntry that references a headerInjection MCPExternalAuthConfig via spec.externalAuthConfigRef produces a vMCP pod that crashes on startup. The generated vMCP configuration contains the header-injection outgoing auth for that backend (referencing the env var TOOLHIVE_HEADER_INJECTION_VALUE_<CONFIG_NAME>), but the operator does not add that env var (secretKeyRef) to the vMCP Deployment.

Environment

  • ToolHive operator / vMCP v0.53.0 (Kubernetes, Helm chart 0.53.0)
  • VirtualMCPServer with outgoingAuth.source: inline (other backends configured under outgoingAuth.backends, one of them upstreamInject)
  • Backends as MCPServerEntry (remote streamable-HTTP servers in other namespaces)

Steps to reproduce

  1. MCPExternalAuthConfig my-backend-auth of type: headerInjection (headerName: Authorization, valueSecretRef to an existing secret/key).
  2. MCPServerEntry my-backend in the vMCP's group with spec.externalAuthConfigRef: {name: my-backend-auth}.
  3. VirtualMCPServer with spec.outgoingAuth.source: inline (no entry for my-backend under outgoingAuth.backends).

Observed

  • MCPServerEntry and MCPExternalAuthConfig are Valid; the operator rolls the vMCP Deployment.
  • The new vMCP pod exits:
    Failed to load configuration: failed to process config: outgoingAuth: backend my-backend: environment variable TOOLHIVE_HEADER_INJECTION_VALUE_MY_BACKEND_AUTH not set or empty
    
  • The Deployment has no env var TOOLHIVE_HEADER_INJECTION_VALUE_MY_BACKEND_AUTH (only those for configs referenced under spec.outgoingAuth.backends).
  • Old pods keep running thanks to the PDB, the rollout hangs.

Workaround (works)

Reference the same config under VirtualMCPServer.spec.outgoingAuth.backends.my-backend (type: externalAuthConfigRef, externalAuthConfigRef: {name: my-backend-auth}) and remove it from the MCPServerEntry. The operator then mounts the secret and the backend works.

Analysis (v0.53.0)

buildOutgoingAuthEnvVars mounts secrets for ExternalAuthConfigs discovered on workloads (MCPServers, MCPRemoteProxies, MCPServerEntries) only when OutgoingAuth.Source == discovered; in inline mode it mounts only spec.outgoingAuth.backends and default. The generated vMCP config, however, does include the entry's header-injection auth in inline mode. Config generation and env-var mounting disagree for this combination.

Expected

Either mount the secret in inline mode as well (when an MCPServerEntry carries externalAuthConfigRef and no inline override exists), or do not put the entry's auth into the config in inline mode and surface a clear condition/warning on the VirtualMCPServer/MCPServerEntry ("externalAuthConfigRef on MCPServerEntry is ignored with outgoingAuth.source=inline"). Today the result is a crash-looping vMCP with a hard-to-trace error.

Activity

  1. hellouz818 commented on Oct 16, 2026

    @hellouz818
    Contributor

    Looking at main (87ed79d94), I see the same mismatch. The affected secret-handling path also covers other auth types.

    Same discovery, different gate. buildOutgoingAuthConfig calls discoverExternalAuthConfigs unconditionally, while buildOutgoingAuthEnvVars calls discoverExternalAuthConfigSecrets only when source == discovered. Both use getExternalAuthConfigNameFromWorkload to resolve workload refs.

    Scope beyond headerInjection. getExternalAuthConfigSecretEnvVars emits env vars for tokenExchange, headerInjection, obo and xaa, and the helper above covers MCPServer and MCPRemoteProxy as well as MCPServerEntry. headerInjection, tokenExchange and obo have secret env-var checks in processBackendAuthStrategy at config load, explaining the startup failure for headerInjection reported here. xaa also depends on secret env vars, although its validation path differs from the YAML loader checks above.

    On the two expectations you listed. In inline mode, OutgoingAuthConfig.ApplyToBackend uses config-based auth, so the strategy the operator wrote into the ConfigMap is the one used at runtime. Mounting the required secret in inline mode would therefore preserve the existing auth selection behavior. Dropping workload-referenced auth from the config instead would also change behavior for strategies that require no secret env vars (unauthenticated, awsSts, upstreamInject).

    One note on test coverage: the case named "inline mode ignores discovered configs" uses the same MCPExternalAuthConfig for both the workload ref and the inline override, so it doesn't distinguish the two paths or cover the no-override combination reported here. "Discovered" also means two different things in this area: the operator reading externalAuthConfigRef off a workload, and the runtime's source: discovered backend watcher.

    Open question for the fix: whether the env path should mirror the shouldAssign skip so secrets aren't mounted for backends overridden inline, or whether the env vars should be derived from the effective auth configuration. The latter could help keep the two paths aligned, but would be a broader change.

    @nilsberberich, if you're planning to fix this yourself, I'm happy to leave it with you. Otherwise, I'd like to pick it up, with tests for the uncovered combination. Could a maintainer assign it to me?

  2. jhrozek commented on Oct 16, 2026

    @jhrozek
    Contributor

    @hellouz818 thank you for volunteerig to take this issue! Assigned to you!

  3. nilsberberich commented on Oct 16, 2026

    @nilsberberich
    Author

    @hellouz818 thanks for the thorough analysis, and yes, please go ahead — I'm not working on a fix myself. Happy to verify the PR in our test cluster with the reported combination (MCPServerEntry with externalAuthConfigRef (headerInjection) behind a VirtualMCPServer with outgoingAuth.source: inline) once it's up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

needs-triageIssue needs initial triage by a maintainer

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions