Skip to content
12 changes: 6 additions & 6 deletions cmd/thv-operator/controllers/virtualmcpserver_vmcpconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,13 @@ func TestConvertBackendAuthConfig(t *testing.T) {
name string
authConfig *mcpv1alpha1.BackendAuthConfig
expectedType string
hasMetadata bool
}{
{
name: "discovered",
authConfig: &mcpv1alpha1.BackendAuthConfig{
Type: mcpv1alpha1.BackendAuthTypeDiscovered,
},
expectedType: mcpv1alpha1.BackendAuthTypeDiscovered,
hasMetadata: false,
},
{
name: "external auth config ref",
Expand All @@ -181,7 +179,6 @@ func TestConvertBackendAuthConfig(t *testing.T) {
},
},
expectedType: mcpv1alpha1.BackendAuthTypeExternalAuthConfigRef,
hasMetadata: true,
},
}

Expand Down Expand Up @@ -212,9 +209,12 @@ func TestConvertBackendAuthConfig(t *testing.T) {
require.NotNil(t, strategy)
assert.Equal(t, tt.expectedType, strategy.Type)

if tt.hasMetadata {
assert.NotEmpty(t, strategy.Metadata)
}
// Note: HeaderInjection and TokenExchange are nil because the CRD's
// BackendAuthConfig only stores type and reference information.
// For external_auth_config_ref, the actual auth config is resolved
// at runtime from the referenced MCPExternalAuthConfig resource.
assert.Nil(t, strategy.HeaderInjection)
assert.Nil(t, strategy.TokenExchange)
})
}
}
Expand Down
26 changes: 14 additions & 12 deletions cmd/thv-operator/pkg/vmcpconfig/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"

mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1"
authtypes "github.com/stacklok/toolhive/pkg/vmcp/auth/types"
vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config"
)

Expand Down Expand Up @@ -157,7 +158,7 @@ func (c *Converter) convertOutgoingAuth(
) *vmcpconfig.OutgoingAuthConfig {
outgoing := &vmcpconfig.OutgoingAuthConfig{
Source: vmcp.Spec.OutgoingAuth.Source,
Backends: make(map[string]*vmcpconfig.BackendAuthStrategy),
Backends: make(map[string]*authtypes.BackendAuthStrategy),
}

// Convert Default
Expand All @@ -174,20 +175,21 @@ func (c *Converter) convertOutgoingAuth(
}

// convertBackendAuthConfig converts BackendAuthConfig from CRD to vmcp config
//
// Note: The CRD's BackendAuthConfig only contains type and reference information.
// For type="external_auth_config_ref", the actual auth config (HeaderInjection or
// TokenExchange) is stored in a separate MCPExternalAuthConfig resource and resolved
// at runtime by the discovery mechanism, not in this static converter.
func (*Converter) convertBackendAuthConfig(
crdConfig *mcpv1alpha1.BackendAuthConfig,
) *vmcpconfig.BackendAuthStrategy {
strategy := &vmcpconfig.BackendAuthStrategy{
Type: crdConfig.Type,
Metadata: make(map[string]any),
}

// Convert type-specific configuration to metadata
if crdConfig.ExternalAuthConfigRef != nil {
strategy.Metadata["externalAuthConfigRef"] = crdConfig.ExternalAuthConfigRef.Name
) *authtypes.BackendAuthStrategy {
return &authtypes.BackendAuthStrategy{
Type: crdConfig.Type,
// HeaderInjection and TokenExchange are nil here because the CRD's
// BackendAuthConfig references external configuration via ExternalAuthConfigRef.
// The actual auth strategy details are resolved at runtime from the
// referenced MCPExternalAuthConfig resource.
}

return strategy
}

// convertAggregation converts AggregationConfig from CRD to vmcp config
Expand Down
17 changes: 9 additions & 8 deletions pkg/vmcp/aggregator/discoverer.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,12 @@ func (d *backendDiscoverer) applyAuthConfigToBackend(backend *vmcp.Backend, back
// In discovered mode, use auth discovered from MCPServer (if any exists)
// If no auth is discovered, fall back to config-based auth via ResolveForBackend
// which will use backend-specific config, then Default, then no auth
useDiscoveredAuth = backend.AuthStrategy != ""
useDiscoveredAuth = backend.AuthConfig != nil && backend.AuthConfig.Type != ""
case "mixed":
// In mixed mode, use discovered auth as default, but allow config overrides
// If there's no explicit config for this backend, use discovered auth
_, hasExplicitConfig := d.authConfig.Backends[backendName]
useDiscoveredAuth = !hasExplicitConfig && backend.AuthStrategy != ""
useDiscoveredAuth = !hasExplicitConfig && backend.AuthConfig != nil && backend.AuthConfig.Type != ""
case "inline", "":
// For inline mode or empty source, always use config-based auth
// Ignore any discovered auth from backends
Expand All @@ -197,14 +197,15 @@ func (d *backendDiscoverer) applyAuthConfigToBackend(backend *vmcp.Backend, back

if useDiscoveredAuth {
// Keep the auth discovered from MCPServer (already populated in backend)
logger.Debugf("Backend %s using discovered auth strategy: %s", backendName, backend.AuthStrategy)
if backend.AuthConfig != nil {
logger.Debugf("Backend %s using discovered auth strategy: %s", backendName, backend.AuthConfig.Type)
}
} else {
// Use auth from config (inline mode or explicit override in mixed mode)
authStrategy, authMetadata := d.authConfig.ResolveForBackend(backendName)
if authStrategy != "" {
backend.AuthStrategy = authStrategy
backend.AuthMetadata = authMetadata
logger.Debugf("Backend %s configured with auth strategy from config: %s", backendName, authStrategy)
authConfig := d.authConfig.ResolveForBackend(backendName)
if authConfig != nil && authConfig.Type != "" {
backend.AuthConfig = authConfig
logger.Debugf("Backend %s configured with auth strategy from config: %s", backendName, authConfig.Type)
}
}
}
Loading
Loading