Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions cmd/thv-operator/api/v1alpha1/mcpserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,14 +632,6 @@ type ConfigMapAuthzRef struct {
Key string `json:"key,omitempty"`
}

// ToolConfigRef defines a reference to a MCPToolConfig resource.
// The referenced MCPToolConfig must be in the same namespace as the MCPServer.
type ToolConfigRef struct {
// Name is the name of the MCPToolConfig resource in the same namespace
// +kubebuilder:validation:Required
Name string `json:"name"`
}

// ExternalAuthConfigRef defines a reference to a MCPExternalAuthConfig resource.
// The referenced MCPExternalAuthConfig must be in the same namespace as the MCPServer.
type ExternalAuthConfigRef struct {
Expand All @@ -648,6 +640,14 @@ type ExternalAuthConfigRef struct {
Name string `json:"name"`
}

// ToolConfigRef defines a reference to a MCPToolConfig resource.
// The referenced MCPToolConfig must be in the same namespace as the MCPServer.
type ToolConfigRef struct {
// Name is the name of the MCPToolConfig resource in the same namespace
// +kubebuilder:validation:Required
Name string `json:"name"`
}

// InlineAuthzConfig contains direct authorization configuration
type InlineAuthzConfig struct {
// Policies is a list of Cedar policy strings
Expand Down
40 changes: 40 additions & 0 deletions cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ type VirtualMCPServerSpec struct {
// The referenced EmbeddingServer must exist in the same namespace and be ready.
// +optional
EmbeddingServerRef *EmbeddingServerRef `json:"embeddingServerRef,omitempty"`

// AuthServerConfig configures an embedded OAuth authorization server.
// When set, the vMCP server acts as an OIDC issuer, drives users through
// upstream IDPs, and issues ToolHive JWTs. The embedded AS becomes the
// IncomingAuth OIDC provider — its issuer must match IncomingAuth.OIDCConfig
// so that tokens it issues are accepted by the vMCP's incoming auth middleware.
// When nil, IncomingAuth uses an external IDP and behavior is unchanged.
// +optional
AuthServerConfig *EmbeddedAuthServerConfig `json:"authServerConfig,omitempty"`
}

// EmbeddingServerRef references an existing EmbeddingServer resource by name.
Expand Down Expand Up @@ -225,6 +234,9 @@ const (

// ConditionTypeEmbeddingServerReady indicates whether the EmbeddingServer is ready
ConditionTypeEmbeddingServerReady = "EmbeddingServerReady"

// ConditionTypeAuthServerConfigValidated indicates whether the AuthServerConfig has been validated
ConditionTypeAuthServerConfigValidated = "AuthServerConfigValidated"
)

// Condition reasons for VirtualMCPServer
Expand Down Expand Up @@ -282,6 +294,12 @@ const (

// ConditionReasonEmbeddingServerNotReady indicates the referenced EmbeddingServer is not ready
ConditionReasonEmbeddingServerNotReady = "EmbeddingServerNotReady"

// ConditionReasonAuthServerConfigValid indicates the AuthServerConfig is valid
ConditionReasonAuthServerConfigValid = "AuthServerConfigValid"

// ConditionReasonAuthServerConfigInvalid indicates the AuthServerConfig is invalid
ConditionReasonAuthServerConfigInvalid = "AuthServerConfigInvalid"
)

// Backend authentication types
Expand Down Expand Up @@ -393,10 +411,32 @@ func (r *VirtualMCPServer) Validate() error {
}
}

// Validate AuthServerConfig (inline embedded auth server)
if err := r.validateAuthServerConfig(); err != nil {
return err
}

// Validate EmbeddingServer / EmbeddingServerRef
return r.validateEmbeddingServer()
}

// validateAuthServerConfig validates inline AuthServerConfig.
// Rules:
// - issuer must be non-empty when config is provided
// - at least one upstream provider must be configured
func (r *VirtualMCPServer) validateAuthServerConfig() error {
if r.Spec.AuthServerConfig == nil {
return nil
}
if r.Spec.AuthServerConfig.Issuer == "" {
return fmt.Errorf("spec.authServerConfig.issuer is required")
}
if len(r.Spec.AuthServerConfig.UpstreamProviders) == 0 {
return fmt.Errorf("spec.authServerConfig.upstreamProviders is required")
}
return nil
}

// validateEmbeddingServer validates EmbeddingServerRef and Optimizer configuration.
// Rules:
// - embeddingServerRef.name must be non-empty when ref is provided
Expand Down
86 changes: 86 additions & 0 deletions cmd/thv-operator/api/v1alpha1/virtualmcpserver_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,89 @@ func TestValidateEmbeddingServer(t *testing.T) {
})
}
}

func TestValidateAuthServerConfig(t *testing.T) {
t.Parallel()

tests := []struct {
name string
server *VirtualMCPServer
expectError bool
errContains string
}{
{
name: "nil_config_passes",
server: &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
Config: config.Config{Group: "test-group"},
},
},
},
{
name: "valid_config_passes",
server: &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
Config: config.Config{Group: "test-group"},
AuthServerConfig: &EmbeddedAuthServerConfig{
Issuer: "http://localhost:9090",
UpstreamProviders: []UpstreamProviderConfig{
{
Name: "test",
Type: UpstreamProviderTypeOIDC,
OIDCConfig: &OIDCUpstreamConfig{
IssuerURL: "https://accounts.google.com",
ClientID: "test-client",
},
},
},
},
},
},
},
{
name: "empty_issuer_errors",
server: &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
Config: config.Config{Group: "test-group"},
AuthServerConfig: &EmbeddedAuthServerConfig{
Issuer: "",
UpstreamProviders: []UpstreamProviderConfig{
{Name: "test", Type: UpstreamProviderTypeOIDC},
},
},
},
},
expectError: true,
errContains: "spec.authServerConfig.issuer is required",
},
{
name: "no_upstreams_errors",
server: &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
Config: config.Config{Group: "test-group"},
AuthServerConfig: &EmbeddedAuthServerConfig{
Issuer: "http://localhost:9090",
},
},
},
expectError: true,
errContains: "spec.authServerConfig.upstreamProviders is required",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := tt.server.Validate()
if tt.expectError {
require.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
require.NoError(t, err)
})
}
}
5 changes: 5 additions & 0 deletions cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading