From bb7308718937de5535bd599832f21e600597ddfe Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Tue, 6 Jan 2026 13:02:03 -0800 Subject: [PATCH 1/4] Vmcp: migrate groupref into config --- .../api/v1alpha1/virtualmcpserver_types.go | 13 -- .../v1alpha1/virtualmcpserver_types_test.go | 26 ++-- .../api/v1alpha1/virtualmcpserver_webhook.go | 6 +- .../v1alpha1/virtualmcpserver_webhook_test.go | 94 +++++++------- .../api/v1alpha1/zz_generated.deepcopy.go | 16 --- .../virtualmcpserver_controller.go | 30 ++--- .../virtualmcpserver_controller_test.go | 121 +++++------------- .../virtualmcpserver_deployment_test.go | 25 +--- ...virtualmcpserver_discover_backends_test.go | 25 +--- .../virtualmcpserver_externalauth_test.go | 16 +-- ...cpserver_podtemplatespec_reconcile_test.go | 17 +-- .../virtualmcpserver_vmcpconfig_test.go | 68 ++++------ .../virtualmcpserver_watch_test.go | 113 +++++----------- cmd/thv-operator/pkg/vmcpconfig/converter.go | 2 +- .../pkg/vmcpconfig/converter_test.go | 44 +++---- ...rtualmcpserver_compositetool_watch_test.go | 13 +- ...lmcpserver_elicitation_integration_test.go | 13 +- ...irtualmcpserver_externalauth_watch_test.go | 5 +- ...server_podtemplatespec_integration_test.go | 13 +- ...olhive.stacklok.dev_virtualmcpservers.yaml | 18 +-- ...olhive.stacklok.dev_virtualmcpservers.yaml | 18 +-- docs/operator/crd-api.md | 17 --- ...virtualmcpcompositetooldefinition-guide.md | 4 +- docs/operator/virtualmcpserver-api.md | 19 ++- .../virtualmcpserver-kubernetes-guide.md | 10 +- .../virtualmcpserver-observability.md | 4 +- pkg/vmcp/config/config.go | 4 +- .../virtualmcp_aggregation_filtering_test.go | 5 +- .../virtualmcp_aggregation_overrides_test.go | 5 +- .../virtualmcp_auth_discovery_test.go | 5 +- ...irtualmcp_composite_defaultresults_test.go | 5 +- .../virtualmcp_composite_parallel_test.go | 5 +- .../virtualmcp_composite_referenced_test.go | 5 +- .../virtualmcp_composite_sequential_test.go | 5 +- .../virtualmcp_conflict_resolution_test.go | 5 +- .../virtualmcp_discovered_mode_test.go | 5 +- .../virtualmcp_external_auth_test.go | 17 +-- .../virtualmcp/virtualmcp_lifecycle_test.go | 5 +- .../virtualmcp/virtualmcp_toolconfig_test.go | 9 +- .../virtualmcp_yardstick_base_test.go | 5 +- 40 files changed, 279 insertions(+), 556 deletions(-) diff --git a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go index e1b68d5072..833effc6ad 100644 --- a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go +++ b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go @@ -9,12 +9,6 @@ import ( // VirtualMCPServerSpec defines the desired state of VirtualMCPServer type VirtualMCPServerSpec struct { - // GroupRef references an existing MCPGroup that defines backend workloads - // The referenced MCPGroup must exist in the same namespace - // TODO(jerm-dro): migrate to the Config field. - // +kubebuilder:validation:Required - GroupRef GroupRef `json:"groupRef"` - // IncomingAuth configures authentication for clients connecting to the Virtual MCP server // Must be explicitly set - use "anonymous" type when no authentication is required // TODO(jerm-dro): migrate to the Config field. @@ -85,13 +79,6 @@ type VirtualMCPServerSpec struct { Config config.Config `json:"config,omitempty"` } -// GroupRef references an MCPGroup resource -type GroupRef struct { - // Name is the name of the MCPGroup resource in the same namespace - // +kubebuilder:validation:Required - Name string `json:"name"` -} - // IncomingAuthConfig configures authentication for clients connecting to the Virtual MCP server type IncomingAuthConfig struct { // Type defines the authentication type: anonymous or oidc diff --git a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types_test.go b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types_test.go index b662908f1d..52d2d1f7b0 100644 --- a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types_test.go +++ b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_types_test.go @@ -5,6 +5,8 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/stacklok/toolhive/pkg/vmcp/config" ) func TestVirtualMCPServerPhaseTransitions(t *testing.T) { @@ -138,9 +140,7 @@ func TestVirtualMCPServerDefaultValues(t *testing.T) { Namespace: "default", }, Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{ - Name: "test-group", - }, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Source: "", // Should default to "discovered" }, @@ -166,9 +166,7 @@ func TestVirtualMCPServerNamespaceIsolation(t *testing.T) { Namespace: "team-a", }, Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{ - Name: "backend-group", // Must be in team-a namespace - }, + Config: config.Config{Group: "backend-group"}, // Must be in team-a namespace }, } @@ -179,9 +177,7 @@ func TestVirtualMCPServerNamespaceIsolation(t *testing.T) { Namespace: "team-b", }, Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{ - Name: "backend-group", // Different group in team-b namespace - }, + Config: config.Config{Group: "backend-group"}, // Different group in team-b namespace }, } @@ -190,9 +186,9 @@ func TestVirtualMCPServerNamespaceIsolation(t *testing.T) { assert.Equal(t, "vmcp", vmcpTeamB.Name) assert.NotEqual(t, vmcpTeamA.Namespace, vmcpTeamB.Namespace) - // GroupRef names can be the same but refer to different groups in different namespaces - assert.Equal(t, "backend-group", vmcpTeamA.Spec.GroupRef.Name) - assert.Equal(t, "backend-group", vmcpTeamB.Spec.GroupRef.Name) + // Group names can be the same but refer to different groups in different namespaces + assert.Equal(t, "backend-group", vmcpTeamA.Spec.Config.Group) + assert.Equal(t, "backend-group", vmcpTeamB.Spec.Config.Group) } func TestConflictResolutionStrategies(t *testing.T) { @@ -234,7 +230,7 @@ func TestConflictResolutionStrategies(t *testing.T) { vmcp := &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ ConflictResolution: tt.strategy, ConflictResolutionConfig: tt.config, @@ -287,7 +283,7 @@ func TestBackendAuthConfigTypes(t *testing.T) { vmcp := &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Backends: map[string]BackendAuthConfig{ "test-backend": tt.authConfig, @@ -352,7 +348,7 @@ func TestCompositeToolStepDependencies(t *testing.T) { vmcp := &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-workflow", diff --git a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook.go b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook.go index 8b1cf4b8af..b09f78f424 100644 --- a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook.go +++ b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook.go @@ -41,9 +41,9 @@ func (*VirtualMCPServer) ValidateDelete(_ context.Context, _ runtime.Object) (ad // Validate performs validation for VirtualMCPServer // This method can be called by the controller during reconciliation or by the webhook func (r *VirtualMCPServer) Validate() error { - // Validate GroupRef is set (required field) - if r.Spec.GroupRef.Name == "" { - return fmt.Errorf("spec.groupRef.name is required") + // Validate Group is set (required field) + if r.Spec.Config.Group == "" { + return fmt.Errorf("spec.config.groupRef is required") } // Validate IncomingAuth configuration diff --git a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook_test.go b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook_test.go index 13bb1a4152..ef2b568906 100644 --- a/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook_test.go +++ b/cmd/thv-operator/api/v1alpha1/virtualmcpserver_webhook_test.go @@ -2,6 +2,8 @@ package v1alpha1 import ( "testing" + + "github.com/stacklok/toolhive/pkg/vmcp/config" ) func TestVirtualMCPServerValidate(t *testing.T) { @@ -16,9 +18,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid minimal configuration", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{ - Name: "test-group", - }, + Config: config.Config{Group: "test-group"}, }, }, wantErr: false, @@ -27,19 +27,17 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "missing groupRef name", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{ - Name: "", - }, + Config: config.Config{Group: ""}, }, }, wantErr: true, - errMsg: "spec.groupRef.name is required", + errMsg: "spec.config.groupRef is required", }, { name: "empty IncomingAuth type", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, IncomingAuth: &IncomingAuthConfig{ Type: "", }, @@ -52,7 +50,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "OIDC auth without OIDCConfig", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, IncomingAuth: &IncomingAuthConfig{ Type: "oidc", OIDCConfig: nil, @@ -66,7 +64,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid outgoingAuth with discovered source", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Source: "discovered", }, @@ -78,7 +76,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid outgoingAuth source", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Source: "invalid", }, @@ -91,7 +89,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid backend auth type", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Backends: map[string]BackendAuthConfig{ "test-backend": { @@ -108,7 +106,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid backend external auth config ref", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Backends: map[string]BackendAuthConfig{ "test-backend": { @@ -127,7 +125,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid backend external auth config ref - missing name", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, OutgoingAuth: &OutgoingAuthConfig{ Backends: map[string]BackendAuthConfig{ "test-backend": { @@ -145,7 +143,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid aggregation with prefix strategy", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ ConflictResolution: ConflictResolutionPrefix, ConflictResolutionConfig: &ConflictResolutionConfig{ @@ -160,7 +158,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid aggregation with priority strategy", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ ConflictResolution: ConflictResolutionPriority, ConflictResolutionConfig: &ConflictResolutionConfig{ @@ -175,7 +173,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid aggregation - priority strategy without priority order", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ ConflictResolution: ConflictResolutionPriority, ConflictResolutionConfig: &ConflictResolutionConfig{}, @@ -189,7 +187,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid aggregation with tool config ref", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ Tools: []WorkloadToolConfig{ { @@ -208,7 +206,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid aggregation - missing workload name", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ Tools: []WorkloadToolConfig{ { @@ -227,7 +225,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "valid composite tool", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -249,7 +247,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - missing name", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Description: "Test composite tool", @@ -270,7 +268,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - missing description", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -291,7 +289,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - no steps", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -308,7 +306,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - duplicate tool names", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -334,7 +332,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - missing step ID", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -353,7 +351,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - tool step without tool", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -375,7 +373,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - elicitation step without message", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -397,7 +395,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - duplicate step IDs", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -417,7 +415,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid composite tool - invalid step type", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -440,7 +438,7 @@ func TestVirtualMCPServerValidate(t *testing.T) { name: "invalid aggregation - invalid conflict resolution strategy", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, Aggregation: &AggregationConfig{ ConflictResolution: "invalid-strategy", }, @@ -479,7 +477,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid step dependencies", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -498,7 +496,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid forward dependencies", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -517,7 +515,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid error handling with retry", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -542,7 +540,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "invalid error handling action", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -567,7 +565,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "invalid error handling - retry without maxRetries", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -593,7 +591,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid error handling with retryDelay", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -619,7 +617,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid error handling with complex retryDelay", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -645,7 +643,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "invalid error handling - invalid retryDelay format", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -672,7 +670,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "invalid composite tool - unknown dependency reference", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -695,7 +693,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with OnDecline skip_remaining", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -720,7 +718,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with OnDecline abort", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -745,7 +743,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with OnDecline continue", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -770,7 +768,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with OnCancel skip_remaining", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -795,7 +793,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with OnCancel abort", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -820,7 +818,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with OnCancel continue", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -845,7 +843,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "valid elicitation with both OnDecline and OnCancel", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -873,7 +871,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "invalid elicitation - OnDecline with invalid action", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", @@ -899,7 +897,7 @@ func TestValidateCompositeToolsWithDependencies(t *testing.T) { name: "invalid elicitation - OnCancel with invalid action", vmcp: &VirtualMCPServer{ Spec: VirtualMCPServerSpec{ - GroupRef: GroupRef{Name: "test-group"}, + Config: config.Config{Group: "test-group"}, CompositeTools: []CompositeToolSpec{ { Name: "test-tool", diff --git a/cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go b/cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go index 7b2053d043..5bef5bf7a7 100644 --- a/cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -415,21 +415,6 @@ func (in *GitSource) DeepCopy() *GitSource { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GroupRef) DeepCopyInto(out *GroupRef) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupRef. -func (in *GroupRef) DeepCopy() *GroupRef { - if in == nil { - return nil - } - out := new(GroupRef) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HeaderInjectionConfig) DeepCopyInto(out *HeaderInjectionConfig) { *out = *in @@ -2326,7 +2311,6 @@ func (in *VirtualMCPServerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMCPServerSpec) DeepCopyInto(out *VirtualMCPServerSpec) { *out = *in - out.GroupRef = in.GroupRef if in.IncomingAuth != nil { in, out := &in.IncomingAuth, &out.IncomingAuth *out = new(IncomingAuthConfig) diff --git a/cmd/thv-operator/controllers/virtualmcpserver_controller.go b/cmd/thv-operator/controllers/virtualmcpserver_controller.go index 7a98a17fb1..ce4c38bdc3 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_controller.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_controller.go @@ -241,12 +241,12 @@ func (r *VirtualMCPServerReconciler) validateGroupRef( // Validate GroupRef exists mcpGroup := &mcpv1alpha1.MCPGroup{} err := r.Get(ctx, types.NamespacedName{ - Name: vmcp.Spec.GroupRef.Name, + Name: vmcp.Spec.Config.Group, Namespace: vmcp.Namespace, }, mcpGroup) if errors.IsNotFound(err) { - message := fmt.Sprintf("Referenced MCPGroup %s not found", vmcp.Spec.GroupRef.Name) + message := fmt.Sprintf("Referenced MCPGroup %s not found", vmcp.Spec.Config.Group) statusManager.SetPhase(mcpv1alpha1.VirtualMCPServerPhaseFailed) statusManager.SetMessage(message) statusManager.SetGroupRefValidatedCondition( @@ -264,7 +264,7 @@ func (r *VirtualMCPServerReconciler) validateGroupRef( // Check if MCPGroup is ready if mcpGroup.Status.Phase != mcpv1alpha1.MCPGroupPhaseReady { message := fmt.Sprintf("Referenced MCPGroup %s is not ready (phase: %s)", - vmcp.Spec.GroupRef.Name, mcpGroup.Status.Phase) + vmcp.Spec.Config.Group, mcpGroup.Status.Phase) statusManager.SetPhase(mcpv1alpha1.VirtualMCPServerPhasePending) statusManager.SetMessage(message) statusManager.SetGroupRefValidatedCondition( @@ -274,13 +274,13 @@ func (r *VirtualMCPServerReconciler) validateGroupRef( ) statusManager.SetObservedGeneration(vmcp.Generation) // Requeue to check again later - return fmt.Errorf("MCPGroup %s is not ready", vmcp.Spec.GroupRef.Name) + return fmt.Errorf("MCPGroup %s is not ready", vmcp.Spec.Config.Group) } // GroupRef is valid and ready statusManager.SetGroupRefValidatedCondition( mcpv1alpha1.ConditionReasonVirtualMCPServerGroupRefValid, - fmt.Sprintf("MCPGroup %s is valid and ready", vmcp.Spec.GroupRef.Name), + fmt.Sprintf("MCPGroup %s is valid and ready", vmcp.Spec.Config.Group), metav1.ConditionTrue, ) statusManager.SetObservedGeneration(vmcp.Generation) @@ -458,7 +458,7 @@ func (r *VirtualMCPServerReconciler) ensureAllResources( // This ensures consistency - all functions use the same workload list // rather than listing at different times which could yield different results workloadDiscoverer := workloads.NewK8SDiscovererWithClient(r.Client, vmcp.Namespace) - workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcp.Spec.GroupRef.Name) + workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcp.Spec.Config.Group) if err != nil { ctxLogger.Error(err, "Failed to list workloads in group") return fmt.Errorf("failed to list workloads in group: %w", err) @@ -1527,7 +1527,7 @@ func (r *VirtualMCPServerReconciler) discoverBackends( workloadDiscoverer := workloads.NewK8SDiscovererWithClient(r.Client, vmcp.Namespace) // Get all workloads in the group - typedWorkloads, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcp.Spec.GroupRef.Name) + typedWorkloads, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcp.Spec.Config.Group) if err != nil { return nil, fmt.Errorf("failed to list workloads in group: %w", err) } @@ -1550,7 +1550,7 @@ func (r *VirtualMCPServerReconciler) discoverBackends( backendDiscoverer := aggregator.NewUnifiedBackendDiscoverer(workloadDiscoverer, groupsManager, authConfig) // Discover backends using the aggregator - backends, err := backendDiscoverer.Discover(ctx, vmcp.Spec.GroupRef.Name) + backends, err := backendDiscoverer.Discover(ctx, vmcp.Spec.Config.Group) if err != nil { return nil, fmt.Errorf("failed to discover backends: %w", err) } @@ -1697,7 +1697,7 @@ func (r *VirtualMCPServerReconciler) mapMCPGroupToVirtualMCPServer(ctx context.C var requests []reconcile.Request for _, vmcp := range vmcpList.Items { - if vmcp.Spec.GroupRef.Name == mcpGroup.Name { + if vmcp.Spec.Config.Group == mcpGroup.Name { requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ Name: vmcp.Name, @@ -1768,7 +1768,7 @@ func (r *VirtualMCPServerReconciler) mapMCPServerToVirtualMCPServer(ctx context. var requests []reconcile.Request for _, vmcp := range vmcpList.Items { // Only reconcile if this VirtualMCPServer references an affected MCPGroup - if affectedGroups[vmcp.Spec.GroupRef.Name] { + if affectedGroups[vmcp.Spec.Config.Group] { requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ Name: vmcp.Name, @@ -1777,7 +1777,7 @@ func (r *VirtualMCPServerReconciler) mapMCPServerToVirtualMCPServer(ctx context. }) ctxLogger.V(1).Info("Queuing VirtualMCPServer for reconciliation due to MCPServer change", "virtualMCPServer", vmcp.Name, - "mcpGroup", vmcp.Spec.GroupRef.Name, + "mcpGroup", vmcp.Spec.Config.Group, "mcpServer", mcpServer.Name) } } @@ -1849,7 +1849,7 @@ func (r *VirtualMCPServerReconciler) mapMCPRemoteProxyToVirtualMCPServer( var requests []reconcile.Request for _, vmcp := range vmcpList.Items { // Only reconcile if this VirtualMCPServer references an affected MCPGroup - if affectedGroups[vmcp.Spec.GroupRef.Name] { + if affectedGroups[vmcp.Spec.Config.Group] { requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ Name: vmcp.Name, @@ -1858,7 +1858,7 @@ func (r *VirtualMCPServerReconciler) mapMCPRemoteProxyToVirtualMCPServer( }) ctxLogger.V(1).Info("Queuing VirtualMCPServer for reconciliation due to MCPRemoteProxy change", "virtualMCPServer", vmcp.Name, - "mcpGroup", vmcp.Spec.GroupRef.Name, + "mcpGroup", vmcp.Spec.Config.Group, "mcpRemoteProxy", mcpRemoteProxy.Name) } } @@ -1997,14 +1997,14 @@ func (r *VirtualMCPServerReconciler) mcpGroupBackendsReferenceExternalAuthConfig // Get the MCPGroup to verify it exists mcpGroup := &mcpv1alpha1.MCPGroup{} err := r.Get(ctx, types.NamespacedName{ - Name: vmcp.Spec.GroupRef.Name, + Name: vmcp.Spec.Config.Group, Namespace: vmcp.Namespace, }, mcpGroup) if err != nil { // If we can't get the group, we can't determine if it references the auth config // Return false to avoid false positives ctxLogger.Error(err, "Failed to get MCPGroup for ExternalAuthConfig reference check", - "group", vmcp.Spec.GroupRef.Name, + "group", vmcp.Spec.Config.Group, "vmcp", vmcp.Name) return false } diff --git a/cmd/thv-operator/controllers/virtualmcpserver_controller_test.go b/cmd/thv-operator/controllers/virtualmcpserver_controller_test.go index 0c9328f74c..c5448e323b 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_controller_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_controller_test.go @@ -36,6 +36,7 @@ import ( ctrlutil "github.com/stacklok/toolhive/cmd/thv-operator/pkg/controllerutil" "github.com/stacklok/toolhive/cmd/thv-operator/pkg/runconfig/configmap/checksum" "github.com/stacklok/toolhive/cmd/thv-operator/pkg/virtualmcpserverstatus" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/pkg/vmcp/workloads" ) @@ -65,9 +66,7 @@ func TestVirtualMCPServerValidateGroupRef(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, }, mcpGroup: &mcpv1alpha1.MCPGroup{ @@ -113,9 +112,7 @@ func TestVirtualMCPServerValidateGroupRef(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "missing-group", - }, + Config: vmcpconfig.Config{Group: "missing-group"}, }, }, expectError: true, @@ -130,9 +127,7 @@ func TestVirtualMCPServerValidateGroupRef(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "pending-group", - }, + Config: vmcpconfig.Config{Group: "pending-group"}, }, }, mcpGroup: &mcpv1alpha1.MCPGroup{ @@ -225,9 +220,7 @@ func TestVirtualMCPServerEnsureRBACResources(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -305,9 +298,7 @@ func TestVirtualMCPServerEnsureDeployment(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -390,9 +381,7 @@ func TestVirtualMCPServerEnsureService(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -471,9 +460,7 @@ func TestVirtualMCPServerServiceType(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, ServiceType: tt.serviceType, }, } @@ -504,9 +491,7 @@ func TestVirtualMCPServerServiceNeedsUpdate(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, ServiceType: "ClusterIP", }, } @@ -791,9 +776,7 @@ func TestVirtualMCPServerAuthConfiguredCondition(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -813,9 +796,7 @@ func TestVirtualMCPServerAuthConfiguredCondition(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "oidc", OIDCConfig: &mcpv1alpha1.OIDCConfigRef{ @@ -846,9 +827,7 @@ func TestVirtualMCPServerAuthConfiguredCondition(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "oidc", OIDCConfig: &mcpv1alpha1.OIDCConfigRef{ @@ -889,9 +868,7 @@ func TestVirtualMCPServerAuthConfiguredCondition(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "oidc", OIDCConfig: &mcpv1alpha1.OIDCConfigRef{ @@ -1026,9 +1003,7 @@ func TestVirtualMCPServerApplyStatusUpdates(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } }, @@ -1051,9 +1026,7 @@ func TestVirtualMCPServerApplyStatusUpdates(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } }, @@ -1073,9 +1046,7 @@ func TestVirtualMCPServerApplyStatusUpdates(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } }, @@ -1159,9 +1130,7 @@ func TestVirtualMCPServerApplyStatusUpdates_ResourceNotFound(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -1203,9 +1172,7 @@ func TestVirtualMCPServerEnsureAllResources_Errors(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } }, @@ -1278,9 +1245,7 @@ func TestVirtualMCPServerContainerNeedsUpdate(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -1456,9 +1421,7 @@ func TestVirtualMCPServerContainerNeedsUpdate(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, Operational: &mcpv1alpha1.OperationalConfig{ LogLevel: "debug", }, @@ -1763,9 +1726,7 @@ func TestVirtualMCPServerDeploymentNeedsUpdate(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -1937,9 +1898,7 @@ func TestVirtualMCPServerReconcile_HappyPath(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2072,9 +2031,7 @@ func TestVirtualMCPServerReconcile_ValidateGroupRefError(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "nonexistent-group", - }, + Config: vmcpconfig.Config{Group: "nonexistent-group"}, }, } @@ -2130,9 +2087,7 @@ func TestVirtualMCPServerReconcile_GroupNotReady(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2226,9 +2181,7 @@ func TestVirtualMCPServerEnsureDeployment_ConfigMapNotFound(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2264,9 +2217,7 @@ func TestVirtualMCPServerEnsureDeployment_CreateDeployment(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2323,9 +2274,7 @@ func TestVirtualMCPServerEnsureDeployment_UpdateDeployment(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2408,9 +2357,7 @@ func TestVirtualMCPServerEnsureDeployment_NoUpdateNeeded(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2496,9 +2443,7 @@ func TestVirtualMCPServerEnsureService_CreateService(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } @@ -2540,9 +2485,7 @@ func TestVirtualMCPServerEnsureService_UpdateService(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, ServiceType: "LoadBalancer", }, } @@ -2604,9 +2547,7 @@ func TestVirtualMCPServerEnsureService_NoUpdateNeeded(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: testGroupName, - }, + Config: vmcpconfig.Config{Group: testGroupName}, }, } diff --git a/cmd/thv-operator/controllers/virtualmcpserver_deployment_test.go b/cmd/thv-operator/controllers/virtualmcpserver_deployment_test.go index e82f310ea4..795f6865ea 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_deployment_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_deployment_test.go @@ -28,6 +28,7 @@ import ( mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" ctrlutil "github.com/stacklok/toolhive/cmd/thv-operator/pkg/controllerutil" "github.com/stacklok/toolhive/cmd/thv-operator/pkg/runconfig/configmap/checksum" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/pkg/vmcp/workloads" ) @@ -41,9 +42,7 @@ func TestDeploymentForVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } @@ -93,9 +92,7 @@ func TestBuildContainerArgsForVmcp(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, wantArgs: []string{"serve", "--config=/etc/vmcp-config/config.yaml", "--host=0.0.0.0", "--port=4483"}, @@ -108,9 +105,7 @@ func TestBuildContainerArgsForVmcp(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, Operational: &mcpv1alpha1.OperationalConfig{ LogLevel: "debug", }, @@ -142,9 +137,7 @@ func TestBuildVolumesForVmcp(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } @@ -173,9 +166,7 @@ func TestBuildEnvVarsForVmcp(t *testing.T) { Namespace: "test-namespace", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } @@ -291,9 +282,7 @@ func TestServiceForVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } diff --git a/cmd/thv-operator/controllers/virtualmcpserver_discover_backends_test.go b/cmd/thv-operator/controllers/virtualmcpserver_discover_backends_test.go index c16b1821eb..a6073da323 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_discover_backends_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_discover_backends_test.go @@ -33,6 +33,7 @@ import ( mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" ctrlutil "github.com/stacklok/toolhive/cmd/thv-operator/pkg/controllerutil" "github.com/stacklok/toolhive/cmd/thv-operator/pkg/virtualmcpserverstatus" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" ) // TestVirtualMCPServerDiscoverBackends tests the discoverBackends function @@ -59,9 +60,7 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, mcpGroup: &mcpv1alpha1.MCPGroup{ @@ -120,9 +119,7 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, mcpGroup: &mcpv1alpha1.MCPGroup{ @@ -203,9 +200,7 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, mcpGroup: &mcpv1alpha1.MCPGroup{ @@ -265,9 +260,7 @@ func TestVirtualMCPServerDiscoverBackends(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, mcpGroup: &mcpv1alpha1.MCPGroup{ @@ -429,9 +422,7 @@ func TestVirtualMCPServerStatusManagerDiscoveredBackends(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } @@ -500,9 +491,7 @@ func TestVirtualMCPServerReconcileWithBackendDiscovery(t *testing.T) { Generation: 1, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } diff --git a/cmd/thv-operator/controllers/virtualmcpserver_externalauth_test.go b/cmd/thv-operator/controllers/virtualmcpserver_externalauth_test.go index 5725018ef5..bf78201309 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_externalauth_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_externalauth_test.go @@ -244,7 +244,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -316,7 +316,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", Backends: map[string]mcpv1alpha1.BackendAuthConfig{ @@ -427,7 +427,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "inline", Backends: map[string]mcpv1alpha1.BackendAuthConfig{ @@ -492,7 +492,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", Default: &mcpv1alpha1.BackendAuthConfig{ @@ -535,7 +535,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "inline", Backends: map[string]mcpv1alpha1.BackendAuthConfig{ @@ -584,7 +584,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -624,7 +624,7 @@ func TestBuildOutgoingAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, // No OutgoingAuth specified }, }, @@ -793,7 +793,7 @@ func TestDiscoverBackendsWithExternalAuthConfigIntegration(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, diff --git a/cmd/thv-operator/controllers/virtualmcpserver_podtemplatespec_reconcile_test.go b/cmd/thv-operator/controllers/virtualmcpserver_podtemplatespec_reconcile_test.go index 13b30a5203..1bfe240401 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_podtemplatespec_reconcile_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_podtemplatespec_reconcile_test.go @@ -14,6 +14,7 @@ import ( mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" "github.com/stacklok/toolhive/cmd/thv-operator/pkg/runconfig/configmap/checksum" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/pkg/vmcp/workloads" ) @@ -58,9 +59,7 @@ func TestVirtualMCPServerPodTemplateSpecDeterministic(t *testing.T) { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: groupName, - }, + Config: vmcpconfig.Config{Group: groupName}, PodTemplateSpec: podTemplateSpecToRawExtension(t, podTemplate), }, } @@ -134,9 +133,7 @@ func TestVirtualMCPServerPodTemplateSpecPreservesContainer(t *testing.T) { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: groupName, - }, + Config: vmcpconfig.Config{Group: groupName}, PodTemplateSpec: &runtime.RawExtension{ Raw: []byte(`{"spec":{"nodeSelector":{"disktype":"ssd"}}}`), }, @@ -252,9 +249,7 @@ func TestVirtualMCPServerPodTemplateSpecNeedsUpdate(t *testing.T) { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: groupName, - }, + Config: vmcpconfig.Config{Group: groupName}, PodTemplateSpec: podTemplateSpecToRawExtension(t, &tt.existingPodTemplate), }, } @@ -291,9 +286,7 @@ func TestVirtualMCPServerPodTemplateSpecNeedsUpdate(t *testing.T) { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: groupName, - }, + Config: vmcpconfig.Config{Group: groupName}, PodTemplateSpec: tt.newPodTemplateSpec, }, } diff --git a/cmd/thv-operator/controllers/virtualmcpserver_vmcpconfig_test.go b/cmd/thv-operator/controllers/virtualmcpserver_vmcpconfig_test.go index e4bad07d6e..1fd387b3a3 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_vmcpconfig_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_vmcpconfig_test.go @@ -31,9 +31,9 @@ import ( mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" oidcmocks "github.com/stacklok/toolhive/cmd/thv-operator/pkg/oidc/mocks" - "github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpconfig" + vmcpconfigconv "github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpconfig" "github.com/stacklok/toolhive/pkg/vmcp" - vmcpconfigpkg "github.com/stacklok/toolhive/pkg/vmcp/config" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/pkg/vmcp/workloads" ) @@ -48,12 +48,12 @@ func newNoOpMockResolver(t *testing.T) *oidcmocks.MockResolver { } // newTestConverter creates a Converter with the given resolver, failing the test if creation fails. -func newTestConverter(t *testing.T, resolver *oidcmocks.MockResolver) *vmcpconfig.Converter { +func newTestConverter(t *testing.T, resolver *oidcmocks.MockResolver) *vmcpconfigconv.Converter { t.Helper() scheme := runtime.NewScheme() _ = mcpv1alpha1.AddToScheme(scheme) fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - converter, err := vmcpconfig.NewConverter(resolver, fakeClient) + converter, err := vmcpconfigconv.NewConverter(resolver, fakeClient) require.NoError(t, err) return converter } @@ -76,9 +76,7 @@ func TestCreateVmcpConfigFromVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, expectedName: "test-vmcp", @@ -157,9 +155,7 @@ func TestConvertOutgoingAuth(t *testing.T) { vmcpServer := &mcpv1alpha1.VirtualMCPServer{ Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: tt.outgoingAuth, }, } @@ -221,9 +217,7 @@ func TestConvertBackendAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Default: tt.authConfig, }, @@ -231,7 +225,7 @@ func TestConvertBackendAuthConfig(t *testing.T) { } // For external_auth_config_ref test, create the referenced MCPExternalAuthConfig - var converter *vmcpconfig.Converter + var converter *vmcpconfigconv.Converter if tt.authConfig.Type == mcpv1alpha1.BackendAuthTypeExternalAuthConfigRef { // Create a fake MCPExternalAuthConfig externalAuthConfig := &mcpv1alpha1.MCPExternalAuthConfig{ @@ -252,7 +246,7 @@ func TestConvertBackendAuthConfig(t *testing.T) { WithObjects(externalAuthConfig). Build() var err error - converter, err = vmcpconfig.NewConverter(newNoOpMockResolver(t), fakeClient) + converter, err = vmcpconfigconv.NewConverter(newNoOpMockResolver(t), fakeClient) require.NoError(t, err) } else { converter = newTestConverter(t, newNoOpMockResolver(t)) @@ -344,9 +338,7 @@ func TestConvertAggregation(t *testing.T) { vmcpServer := &mcpv1alpha1.VirtualMCPServer{ Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, Aggregation: tt.aggregation, }, } @@ -437,9 +429,7 @@ func TestConvertCompositeTools(t *testing.T) { vmcpServer := &mcpv1alpha1.VirtualMCPServer{ Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: tt.compositeTools, }, } @@ -470,9 +460,7 @@ func TestEnsureVmcpConfigConfigMap(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, } @@ -502,7 +490,7 @@ func TestEnsureVmcpConfigConfigMap(t *testing.T) { // Fetch workload names (matching production behavior) ctx := context.Background() workloadDiscoverer := workloads.NewK8SDiscovererWithClient(fakeClient, testVmcp.Namespace) - workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, testVmcp.Spec.GroupRef.Name) + workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, testVmcp.Spec.Config.Group) require.NoError(t, err, "should successfully list workloads in group") err = r.ensureVmcpConfigConfigMap(ctx, testVmcp, workloadNames) @@ -543,7 +531,7 @@ func TestValidateVmcpConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - validator := vmcpconfig.NewValidator() + validator := vmcpconfigconv.NewValidator() // Type assertion will fail for nil, which is expected if tt.config == nil { @@ -583,9 +571,7 @@ func TestYAMLMarshalingDeterminism(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, // OutgoingAuth with Backends map OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", @@ -730,9 +716,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_EndToEnd(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -756,7 +740,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_EndToEnd(t *testing.T) { // Fetch workload names (matching production behavior) workloadDiscoverer := workloads.NewK8SDiscovererWithClient(fakeClient, vmcpServer.Namespace) - workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcpServer.Spec.GroupRef.Name) + workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcpServer.Spec.Config.Group) require.NoError(t, err, "should successfully list workloads in group") // Test the ensureVmcpConfigConfigMap function @@ -775,7 +759,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_EndToEnd(t *testing.T) { require.Contains(t, configMap.Data, "config.yaml", "ConfigMap should contain config.yaml") // Parse the YAML config - var config vmcpconfigpkg.Config + var config vmcpconfig.Config err = yaml.Unmarshal([]byte(configMap.Data["config.yaml"]), &config) require.NoError(t, err, "should parse config YAML") @@ -786,7 +770,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_EndToEnd(t *testing.T) { require.Len(t, config.CompositeTools[0].Steps, 1) assert.Equal(t, "step1", config.CompositeTools[0].Steps[0].ID) assert.Equal(t, "backend.echo", config.CompositeTools[0].Steps[0].Tool) - assert.Equal(t, vmcpconfigpkg.Duration(30*time.Second), config.CompositeTools[0].Timeout) + assert.Equal(t, vmcpconfig.Duration(30*time.Second), config.CompositeTools[0].Timeout) // Verify parameters were converted require.NotNil(t, config.CompositeTools[0].Parameters) @@ -841,9 +825,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_MergeInlineAndReferenced(t Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -880,7 +862,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_MergeInlineAndReferenced(t // Fetch workload names (matching production behavior) workloadDiscoverer := workloads.NewK8SDiscovererWithClient(fakeClient, vmcpServer.Namespace) - workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcpServer.Spec.GroupRef.Name) + workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcpServer.Spec.Config.Group) require.NoError(t, err, "should successfully list workloads in group") // Test the ensureVmcpConfigConfigMap function @@ -896,7 +878,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_MergeInlineAndReferenced(t require.NoError(t, err, "ConfigMap should exist") // Parse the YAML config - var config vmcpconfigpkg.Config + var config vmcpconfig.Config err = yaml.Unmarshal([]byte(configMap.Data["config.yaml"]), &config) require.NoError(t, err, "should parse config YAML") @@ -937,9 +919,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_NotFound(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -963,7 +943,7 @@ func TestVirtualMCPServerReconciler_CompositeToolRefs_NotFound(t *testing.T) { // Fetch workload names (matching production behavior) workloadDiscoverer := workloads.NewK8SDiscovererWithClient(fakeClient, vmcpServer.Namespace) - workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcpServer.Spec.GroupRef.Name) + workloadNames, err := workloadDiscoverer.ListWorkloadsInGroup(ctx, vmcpServer.Spec.Config.Group) require.NoError(t, err, "should successfully list workloads in group") // Test should fail with not found error diff --git a/cmd/thv-operator/controllers/virtualmcpserver_watch_test.go b/cmd/thv-operator/controllers/virtualmcpserver_watch_test.go index 64dd634642..659b165811 100644 --- a/cmd/thv-operator/controllers/virtualmcpserver_watch_test.go +++ b/cmd/thv-operator/controllers/virtualmcpserver_watch_test.go @@ -26,6 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" ) // TestMapMCPGroupToVirtualMCPServer tests the MCPGroup watch handler @@ -54,9 +55,7 @@ func TestMapMCPGroupToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, }, @@ -78,9 +77,7 @@ func TestMapMCPGroupToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, { @@ -89,9 +86,7 @@ func TestMapMCPGroupToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, }, @@ -113,9 +108,7 @@ func TestMapMCPGroupToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "other-group", - }, + Config: vmcpconfig.Config{Group: "other-group"}, }, }, }, @@ -137,9 +130,7 @@ func TestMapMCPGroupToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, { @@ -148,9 +139,7 @@ func TestMapMCPGroupToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "other-group", - }, + Config: vmcpconfig.Config{Group: "other-group"}, }, }, }, @@ -268,9 +257,7 @@ func TestMapMCPServerToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, }, @@ -303,9 +290,7 @@ func TestMapMCPServerToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, }, @@ -338,9 +323,7 @@ func TestMapMCPServerToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "other-group", - }, + Config: vmcpconfig.Config{Group: "other-group"}, }, }, }, @@ -382,9 +365,7 @@ func TestMapMCPServerToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "group-1", - }, + Config: vmcpconfig.Config{Group: "group-1"}, }, }, { @@ -393,9 +374,7 @@ func TestMapMCPServerToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "group-2", - }, + Config: vmcpconfig.Config{Group: "group-2"}, }, }, { @@ -404,9 +383,7 @@ func TestMapMCPServerToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "group-3", - }, + Config: vmcpconfig.Config{Group: "group-3"}, }, }, }, @@ -530,9 +507,7 @@ func TestMapMCPRemoteProxyToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, }, @@ -565,9 +540,7 @@ func TestMapMCPRemoteProxyToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, }, @@ -600,9 +573,7 @@ func TestMapMCPRemoteProxyToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "other-group", - }, + Config: vmcpconfig.Config{Group: "other-group"}, }, }, }, @@ -644,9 +615,7 @@ func TestMapMCPRemoteProxyToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "group-1", - }, + Config: vmcpconfig.Config{Group: "group-1"}, }, }, { @@ -655,9 +624,7 @@ func TestMapMCPRemoteProxyToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "group-2", - }, + Config: vmcpconfig.Config{Group: "group-2"}, }, }, { @@ -666,9 +633,7 @@ func TestMapMCPRemoteProxyToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "group-3", - }, + Config: vmcpconfig.Config{Group: "group-3"}, }, }, }, @@ -912,9 +877,7 @@ func TestMapExternalAuthConfigToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -961,9 +924,7 @@ func TestMapExternalAuthConfigToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1010,9 +971,7 @@ func TestMapExternalAuthConfigToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1059,9 +1018,7 @@ func TestMapExternalAuthConfigToVirtualMCPServer(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1538,9 +1495,7 @@ func TestVmcpReferencesExternalAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1579,9 +1534,7 @@ func TestVmcpReferencesExternalAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1620,9 +1573,7 @@ func TestVmcpReferencesExternalAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "nonexistent-group", - }, + Config: vmcpconfig.Config{Group: "nonexistent-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1639,9 +1590,7 @@ func TestVmcpReferencesExternalAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1701,9 +1650,7 @@ func TestVmcpReferencesExternalAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, @@ -1742,9 +1689,7 @@ func TestVmcpReferencesExternalAuthConfig(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: "test-group", - }, + Config: vmcpconfig.Config{Group: "test-group"}, OutgoingAuth: &mcpv1alpha1.OutgoingAuthConfig{ Source: "discovered", }, diff --git a/cmd/thv-operator/pkg/vmcpconfig/converter.go b/cmd/thv-operator/pkg/vmcpconfig/converter.go index 9532d79ccf..c8e0060ed2 100644 --- a/cmd/thv-operator/pkg/vmcpconfig/converter.go +++ b/cmd/thv-operator/pkg/vmcpconfig/converter.go @@ -67,7 +67,7 @@ func (c *Converter) Convert( ) (*vmcpconfig.Config, error) { config := &vmcpconfig.Config{ Name: vmcp.Name, - Group: vmcp.Spec.GroupRef.Name, + Group: vmcp.Spec.Config.Group, } // Convert IncomingAuth - required field, no defaults diff --git a/cmd/thv-operator/pkg/vmcpconfig/converter_test.go b/cmd/thv-operator/pkg/vmcpconfig/converter_test.go index 5d4ced3c52..51a4d2edb9 100644 --- a/cmd/thv-operator/pkg/vmcpconfig/converter_test.go +++ b/cmd/thv-operator/pkg/vmcpconfig/converter_test.go @@ -62,7 +62,7 @@ func newTestVMCPServer(oidcConfig *mcpv1alpha1.OIDCConfigRef) *mcpv1alpha1.Virtu return &mcpv1alpha1.VirtualMCPServer{ ObjectMeta: metav1.ObjectMeta{Name: "test-vmcp", Namespace: "default"}, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{Type: "oidc", OIDCConfig: oidcConfig}, }, } @@ -281,7 +281,7 @@ func TestConvertCompositeTools_Parameters(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -364,7 +364,7 @@ func TestConvertCompositeTools_Timeout(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -488,7 +488,7 @@ func TestConverter_ConvertCompositeTools_ErrorHandling(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -537,7 +537,7 @@ func TestConverter_ConvertCompositeTools_NoErrorHandling(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -656,7 +656,7 @@ func TestConverter_ConvertCompositeTools_ElicitationResponseHandlers(t *testing. Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -714,7 +714,7 @@ func TestConverter_ConvertCompositeTools_StepTimeout(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -802,7 +802,7 @@ func TestConvertCompositeTools_NonStringArguments(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -843,7 +843,7 @@ func TestConvertCompositeTools_NonStringArguments(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -882,7 +882,7 @@ func TestConvertCompositeTools_NonStringArguments(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -1224,7 +1224,7 @@ func TestConverter_ConvertCompositeTools_OutputSpec(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "test-tool", @@ -1346,7 +1346,7 @@ func TestConverter_IncomingAuthRequired(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, IncomingAuth: tt.incomingAuth, }, } @@ -1421,7 +1421,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeToolRefs: []mcpv1alpha1.CompositeToolDefinitionRef{ {Name: "referenced-tool"}, }, @@ -1465,7 +1465,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "inline-tool", @@ -1524,7 +1524,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeToolRefs: []mcpv1alpha1.CompositeToolDefinitionRef{ {Name: "non-existent-tool"}, }, @@ -1542,7 +1542,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeTools: []mcpv1alpha1.CompositeToolSpec{ { Name: "duplicate-tool", @@ -1591,7 +1591,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, compositeDefs: []*mcpv1alpha1.VirtualMCPCompositeToolDefinition{}, @@ -1607,7 +1607,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeToolRefs: []mcpv1alpha1.CompositeToolDefinitionRef{ {Name: "tool1"}, {Name: "tool2"}, @@ -1670,7 +1670,7 @@ func TestConverter_CompositeToolRefs(t *testing.T) { Namespace: "default", }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, CompositeToolRefs: []mcpv1alpha1.CompositeToolDefinitionRef{ {Name: "referenced-tool"}, }, @@ -2165,7 +2165,7 @@ func TestConvert_MCPToolConfigFailClosed(t *testing.T) { vmcp: &mcpv1alpha1.VirtualMCPServer{ ObjectMeta: metav1.ObjectMeta{Name: "test-vmcp", Namespace: "default"}, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, Aggregation: &mcpv1alpha1.AggregationConfig{ Tools: []mcpv1alpha1.WorkloadToolConfig{{ Workload: "backend1", @@ -2183,7 +2183,7 @@ func TestConvert_MCPToolConfigFailClosed(t *testing.T) { vmcp: &mcpv1alpha1.VirtualMCPServer{ ObjectMeta: metav1.ObjectMeta{Name: "test-vmcp", Namespace: "default"}, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, Aggregation: &mcpv1alpha1.AggregationConfig{ Tools: []mcpv1alpha1.WorkloadToolConfig{{ Workload: "backend1", @@ -2200,7 +2200,7 @@ func TestConvert_MCPToolConfigFailClosed(t *testing.T) { vmcp: &mcpv1alpha1.VirtualMCPServer{ ObjectMeta: metav1.ObjectMeta{Name: "test-vmcp", Namespace: "default"}, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{Name: "test-group"}, + Config: vmcpconfig.Config{Group: "test-group"}, }, }, existingConfig: nil, diff --git a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_compositetool_watch_test.go b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_compositetool_watch_test.go index b736e4048f..cc221a96e9 100644 --- a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_compositetool_watch_test.go +++ b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_compositetool_watch_test.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" ) var _ = Describe("VirtualMCPServer CompositeToolDefinition Watch Integration Tests", func() { @@ -68,9 +69,7 @@ var _ = Describe("VirtualMCPServer CompositeToolDefinition Watch Integration Tes Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -269,9 +268,7 @@ var _ = Describe("VirtualMCPServer CompositeToolDefinition Watch Integration Tes Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -391,9 +388,7 @@ var _ = Describe("VirtualMCPServer CompositeToolDefinition Watch Integration Tes Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_elicitation_integration_test.go b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_elicitation_integration_test.go index 21ee96770f..dc5e42ec25 100644 --- a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_elicitation_integration_test.go +++ b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_elicitation_integration_test.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" ) var _ = Describe("VirtualMCPServer Elicitation Integration Tests", func() { @@ -132,9 +133,7 @@ var _ = Describe("VirtualMCPServer Elicitation Integration Tests", func() { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -339,9 +338,7 @@ var _ = Describe("VirtualMCPServer Elicitation Integration Tests", func() { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -527,9 +524,7 @@ var _ = Describe("VirtualMCPServer Elicitation Integration Tests", func() { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_externalauth_watch_test.go b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_externalauth_watch_test.go index 9ceb3781bc..6b05b7b275 100644 --- a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_externalauth_watch_test.go +++ b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_externalauth_watch_test.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" ) var _ = Describe("VirtualMCPServer ExternalAuthConfig Watch Integration Tests", func() { @@ -104,9 +105,7 @@ var _ = Describe("VirtualMCPServer ExternalAuthConfig Watch Integration Tests", Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_podtemplatespec_integration_test.go b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_podtemplatespec_integration_test.go index 8d9ccb2f95..fcf38ad81b 100644 --- a/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_podtemplatespec_integration_test.go +++ b/cmd/thv-operator/test-integration/virtualmcp/virtualmcpserver_podtemplatespec_integration_test.go @@ -14,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" ) var _ = Describe("VirtualMCPServer PodTemplateSpec Integration Tests", func() { @@ -68,9 +69,7 @@ var _ = Describe("VirtualMCPServer PodTemplateSpec Integration Tests", func() { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -209,9 +208,7 @@ var _ = Describe("VirtualMCPServer PodTemplateSpec Integration Tests", func() { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -314,9 +311,7 @@ var _ = Describe("VirtualMCPServer PodTemplateSpec Integration Tests", func() { Namespace: namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml index bc69d0e157..55c23cbcdf 100644 --- a/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml +++ b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml @@ -707,8 +707,9 @@ spec: type: object type: array groupRef: - description: Group references the ToolHive group containing backend - workloads. + description: |- + Group references an existing MCPGroup that defines backend workloads. + In Kubernetes, the referenced MCPGroup must exist in the same namespace. type: string incomingAuth: description: IncomingAuth configures how clients authenticate @@ -1105,18 +1106,6 @@ spec: - name type: object x-kubernetes-preserve-unknown-fields: true - groupRef: - description: |- - GroupRef references an existing MCPGroup that defines backend workloads - The referenced MCPGroup must exist in the same namespace - properties: - name: - description: Name is the name of the MCPGroup resource in the - same namespace - type: string - required: - - name - type: object incomingAuth: description: |- IncomingAuth configures authentication for clients connecting to the Virtual MCP server @@ -1567,7 +1556,6 @@ spec: type: object type: object required: - - groupRef - incomingAuth type: object status: diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml index ee00971bb0..573b47d0be 100644 --- a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml @@ -710,8 +710,9 @@ spec: type: object type: array groupRef: - description: Group references the ToolHive group containing backend - workloads. + description: |- + Group references an existing MCPGroup that defines backend workloads. + In Kubernetes, the referenced MCPGroup must exist in the same namespace. type: string incomingAuth: description: IncomingAuth configures how clients authenticate @@ -1108,18 +1109,6 @@ spec: - name type: object x-kubernetes-preserve-unknown-fields: true - groupRef: - description: |- - GroupRef references an existing MCPGroup that defines backend workloads - The referenced MCPGroup must exist in the same namespace - properties: - name: - description: Name is the name of the MCPGroup resource in the - same namespace - type: string - required: - - name - type: object incomingAuth: description: |- IncomingAuth configures authentication for clients connecting to the Virtual MCP server @@ -1570,7 +1559,6 @@ spec: type: object type: object required: - - groupRef - incomingAuth type: object status: diff --git a/docs/operator/crd-api.md b/docs/operator/crd-api.md index 0d7bc5dfcb..ef65385fc8 100644 --- a/docs/operator/crd-api.md +++ b/docs/operator/crd-api.md @@ -419,22 +419,6 @@ _Appears in:_ | `path` _string_ | Path is the path to the registry file within the repository | registry.json | Pattern: `^.*\.json$`
| -#### GroupRef - - - -GroupRef references an MCPGroup resource - - - -_Appears in:_ -- [VirtualMCPServerSpec](#virtualmcpserverspec) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `name` _string_ | Name is the name of the MCPGroup resource in the same namespace | | Required: \{\}
| - - #### HeaderInjectionConfig @@ -2072,7 +2056,6 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `groupRef` _[GroupRef](#groupref)_ | GroupRef references an existing MCPGroup that defines backend workloads
The referenced MCPGroup must exist in the same namespace | | Required: \{\}
| | `incomingAuth` _[IncomingAuthConfig](#incomingauthconfig)_ | IncomingAuth configures authentication for clients connecting to the Virtual MCP server
Must be explicitly set - use "anonymous" type when no authentication is required | | Required: \{\}
| | `outgoingAuth` _[OutgoingAuthConfig](#outgoingauthconfig)_ | OutgoingAuth configures authentication from Virtual MCP to backend MCPServers | | | | `aggregation` _[AggregationConfig](#aggregationconfig)_ | Aggregation defines tool aggregation and conflict resolution strategies | | | diff --git a/docs/operator/virtualmcpcompositetooldefinition-guide.md b/docs/operator/virtualmcpcompositetooldefinition-guide.md index 3598b5ec11..5bb4ba10ac 100644 --- a/docs/operator/virtualmcpcompositetooldefinition-guide.md +++ b/docs/operator/virtualmcpcompositetooldefinition-guide.md @@ -696,8 +696,8 @@ metadata: name: production-vmcp namespace: default spec: - groupRef: - name: production-backends + config: + groupRef: production-backends # Reference composite tool definitions compositeToolRefs: diff --git a/docs/operator/virtualmcpserver-api.md b/docs/operator/virtualmcpserver-api.md index 14a70f2397..15d9871790 100644 --- a/docs/operator/virtualmcpserver-api.md +++ b/docs/operator/virtualmcpserver-api.md @@ -24,20 +24,17 @@ The `VirtualMCPServer` CRD enables aggregation of multiple backend MCPServers in ## Spec Fields -### `.spec.groupRef` (required) +### `.spec.config.groupRef` (required) References an existing `MCPGroup` that defines the backend workloads to aggregate. -**Type**: `GroupRef` - -**Fields**: -- `name` (string, required): Name of the MCPGroup resource in the same namespace +**Type**: `string` **Example**: ```yaml spec: - groupRef: - name: engineering-team + config: + groupRef: engineering-team ``` ### `.spec.incomingAuth` (optional) @@ -411,8 +408,8 @@ metadata: namespace: default spec: # Reference to MCPGroup defining backend workloads - groupRef: - name: engineering-team + config: + groupRef: engineering-team # Client authentication incomingAuth: @@ -558,9 +555,9 @@ status: The VirtualMCPServer CRD includes comprehensive validation: 1. **Required Fields**: - - `spec.groupRef.name` must be specified + - `spec.config.groupRef` must be specified - `spec.incomingAuth.type` must be explicitly specified (use `anonymous` when no auth is needed) -2. **Reference Validation**: All references (groupRef, authConfigRef, toolConfigRef) must be valid +2. **Reference Validation**: All references (config.groupRef, authConfigRef, toolConfigRef) must be valid 3. **Conflict Resolution**: Priority strategy requires `priorityOrder` configuration 4. **Composite Tools**: Must have unique names, valid steps with IDs, and proper dependencies 5. **Token Cache**: Redis provider requires valid address configuration diff --git a/docs/operator/virtualmcpserver-kubernetes-guide.md b/docs/operator/virtualmcpserver-kubernetes-guide.md index 2223372cc5..ba5ffaa392 100644 --- a/docs/operator/virtualmcpserver-kubernetes-guide.md +++ b/docs/operator/virtualmcpserver-kubernetes-guide.md @@ -205,8 +205,8 @@ metadata: name: my-vmcp namespace: default spec: - groupRef: - name: my-services + config: + groupRef: my-services # Configure authentication (adjust from CLI if using OIDC) incomingAuth: @@ -365,8 +365,8 @@ kind: VirtualMCPServer metadata: name: services-vmcp spec: - groupRef: - name: services + config: + groupRef: services incomingAuth: type: anonymous outgoingAuth: @@ -451,7 +451,7 @@ kubectl get virtualmcpserver my-vmcp -o yaml | grep -A 5 conditions kubectl get mcpgroup ``` -Create if missing or fix the `groupRef.name` in VirtualMCPServer spec. +Create if missing or fix `spec.config.groupRef` in VirtualMCPServer spec. **2. No Backend MCPServers in Group** diff --git a/docs/operator/virtualmcpserver-observability.md b/docs/operator/virtualmcpserver-observability.md index 048c1a7d6d..d2608511bc 100644 --- a/docs/operator/virtualmcpserver-observability.md +++ b/docs/operator/virtualmcpserver-observability.md @@ -54,8 +54,8 @@ kind: VirtualMCPServer metadata: name: my-vmcp spec: - groupRef: - name: my-group + config: + groupRef: my-group incomingAuth: type: anonymous telemetry: diff --git a/pkg/vmcp/config/config.go b/pkg/vmcp/config/config.go index 3392833b8b..df953ddb66 100644 --- a/pkg/vmcp/config/config.go +++ b/pkg/vmcp/config/config.go @@ -71,7 +71,9 @@ type Config struct { // Name is the virtual MCP server name. Name string `json:"name" yaml:"name"` - // Group references the ToolHive group containing backend workloads. + // Group references an existing MCPGroup that defines backend workloads. + // In Kubernetes, the referenced MCPGroup must exist in the same namespace. + // +kubebuilder:validation:Required Group string `json:"groupRef" yaml:"groupRef"` // IncomingAuth configures how clients authenticate to the virtual MCP server. diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_filtering_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_filtering_test.go index 9a36c43812..da6a55219d 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_filtering_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_filtering_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -45,9 +46,7 @@ var _ = Describe("VirtualMCPServer Aggregation Filtering", Ordered, func() { Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_overrides_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_overrides_test.go index 94ca911ff1..bda565dda0 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_overrides_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_aggregation_overrides_test.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -46,9 +47,7 @@ var _ = Describe("VirtualMCPServer Tool Overrides", Ordered, func() { Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_auth_discovery_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_auth_discovery_test.go index 3d070501c6..970e798972 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_auth_discovery_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_auth_discovery_test.go @@ -22,6 +22,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -820,9 +821,7 @@ with socketserver.TCPServer(("", PORT), OIDCHandler) as httpd: Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, // OIDC incoming auth - clients must present valid OIDC tokens // vMCP will validate tokens and then exchange them for backend-specific tokens IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_defaultresults_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_defaultresults_test.go index e6ea3b7299..1aa6b345a8 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_defaultresults_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_defaultresults_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -74,9 +75,7 @@ var _ = Describe("VirtualMCPServer Composite Tool DefaultResults", Ordered, func Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_parallel_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_parallel_test.go index 96cf678ed8..92cbaaf31c 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_parallel_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_parallel_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -79,9 +80,7 @@ var _ = Describe("VirtualMCPServer Composite Parallel Workflow", Ordered, func() Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_referenced_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_referenced_test.go index 6e73435b9f..a20d335eda 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_referenced_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_referenced_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -109,9 +110,7 @@ var _ = Describe("VirtualMCPServer Composite Referenced Workflow", Ordered, func Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_sequential_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_sequential_test.go index db868b6b32..17830c3424 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_sequential_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_composite_sequential_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -72,9 +73,7 @@ var _ = Describe("VirtualMCPServer Composite Sequential Workflow", Ordered, func Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_conflict_resolution_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_conflict_resolution_test.go index ad4aa1f256..96061d8ac0 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_conflict_resolution_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_conflict_resolution_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -48,9 +49,7 @@ func setupConflictResolutionTest(setup conflictResolutionTestSetup) int32 { Namespace: setup.namespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: setup.groupName, - }, + Config: vmcpconfig.Config{Group: setup.groupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_discovered_mode_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_discovered_mode_test.go index 47c5295070..92290e7cc2 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_discovered_mode_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_discovered_mode_test.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -113,9 +114,7 @@ var _ = Describe("VirtualMCPServer Discovered Mode", Ordered, func() { Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_external_auth_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_external_auth_test.go index 6dbd96307b..a1b1303ac8 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_external_auth_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_external_auth_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -93,9 +94,7 @@ var _ = Describe("VirtualMCPServer Unauthenticated Backend Auth", Ordered, func( Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -266,9 +265,7 @@ var _ = Describe("VirtualMCPServer Inline Unauthenticated Backend Auth", Ordered Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -448,9 +445,7 @@ var _ = Describe("VirtualMCPServer HeaderInjection Backend Auth", Ordered, func( Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -669,9 +664,7 @@ var _ = Describe("VirtualMCPServer Inline HeaderInjection Backend Auth", Ordered Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go index 03493cc6e8..2211ed5542 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go @@ -19,6 +19,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -88,9 +89,7 @@ var _ = Describe("VirtualMCPServer Lifecycle - DynamicRegistry", Ordered, Pendin Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_toolconfig_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_toolconfig_test.go index 6604f76b49..d7a388deab 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_toolconfig_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_toolconfig_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -67,9 +68,7 @@ var _ = Describe("VirtualMCPServer Tool Filtering via MCPToolConfig", Ordered, f Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, @@ -391,9 +390,7 @@ var _ = Describe("VirtualMCPServer MCPToolConfig Dynamic Updates", Ordered, func Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_yardstick_base_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_yardstick_base_test.go index 64dddd737a..779e13c550 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_yardstick_base_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_yardstick_base_test.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/types" mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1" + vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config" "github.com/stacklok/toolhive/test/e2e/images" ) @@ -108,9 +109,7 @@ var _ = Describe("VirtualMCPServer Yardstick Base", Ordered, func() { Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", }, From c7c89cab92b5c74b8b01e6c1a47b5e481f461c78 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Tue, 6 Jan 2026 13:25:14 -0800 Subject: [PATCH 2/4] update chainsaw tests and examples/ Signed-off-by: Jeremy Drouillard --- .../virtual-mcps/composite_tool_complex.yaml | 4 ++-- .../operator/virtual-mcps/composite_tool_simple.yaml | 4 ++-- .../composite_tool_with_elicitations.yaml | 4 ++-- .../virtual-mcps/vmcp_conflict_resolution.yaml | 12 ++++++------ .../virtual-mcps/vmcp_inline_incoming_auth.yaml | 4 ++-- .../operator/virtual-mcps/vmcp_production_full.yaml | 4 ++-- .../virtual-mcps/vmcp_simple_discovered.yaml | 4 ++-- .../test-scenarios/vmcp/audit-chainsaw-test.yaml | 12 ++++++------ .../test-scenarios/vmcp/basic/chainsaw-test.yaml | 4 ++-- .../test-scenarios/vmcp/vmcp-controller.yaml | 4 ++-- .../test-scenarios/vmcp/vmcp-with-oidc.yaml | 4 ++-- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/examples/operator/virtual-mcps/composite_tool_complex.yaml b/examples/operator/virtual-mcps/composite_tool_complex.yaml index 751967c5cf..dd7f0d7f40 100644 --- a/examples/operator/virtual-mcps/composite_tool_complex.yaml +++ b/examples/operator/virtual-mcps/composite_tool_complex.yaml @@ -288,8 +288,8 @@ metadata: name: vmcp-data-processor namespace: default spec: - groupRef: - name: data-processing-services + config: + groupRef: data-processing-services incomingAuth: type: anonymous diff --git a/examples/operator/virtual-mcps/composite_tool_simple.yaml b/examples/operator/virtual-mcps/composite_tool_simple.yaml index 57d2987fe5..e3e3c6a6f3 100644 --- a/examples/operator/virtual-mcps/composite_tool_simple.yaml +++ b/examples/operator/virtual-mcps/composite_tool_simple.yaml @@ -146,8 +146,8 @@ metadata: name: vmcp-simple-composite namespace: default spec: - groupRef: - name: my-services + config: + groupRef: my-services incomingAuth: type: anonymous diff --git a/examples/operator/virtual-mcps/composite_tool_with_elicitations.yaml b/examples/operator/virtual-mcps/composite_tool_with_elicitations.yaml index 62154f9a8e..357ce97b91 100644 --- a/examples/operator/virtual-mcps/composite_tool_with_elicitations.yaml +++ b/examples/operator/virtual-mcps/composite_tool_with_elicitations.yaml @@ -203,8 +203,8 @@ metadata: name: vmcp-interactive-deploy namespace: default spec: - groupRef: - name: deployment-services + config: + groupRef: deployment-services incomingAuth: type: anonymous diff --git a/examples/operator/virtual-mcps/vmcp_conflict_resolution.yaml b/examples/operator/virtual-mcps/vmcp_conflict_resolution.yaml index 9139e00e83..7ea0d27bac 100644 --- a/examples/operator/virtual-mcps/vmcp_conflict_resolution.yaml +++ b/examples/operator/virtual-mcps/vmcp_conflict_resolution.yaml @@ -100,8 +100,8 @@ metadata: name: vmcp-prefix-strategy namespace: default spec: - groupRef: - name: my-services + config: + groupRef: my-services incomingAuth: type: anonymous @@ -142,8 +142,8 @@ metadata: name: vmcp-priority-strategy namespace: default spec: - groupRef: - name: my-services + config: + groupRef: my-services incomingAuth: type: anonymous @@ -187,8 +187,8 @@ metadata: name: vmcp-manual-strategy namespace: default spec: - groupRef: - name: my-services + config: + groupRef: my-services incomingAuth: type: anonymous diff --git a/examples/operator/virtual-mcps/vmcp_inline_incoming_auth.yaml b/examples/operator/virtual-mcps/vmcp_inline_incoming_auth.yaml index b3b0a3748c..a430edef6d 100644 --- a/examples/operator/virtual-mcps/vmcp_inline_incoming_auth.yaml +++ b/examples/operator/virtual-mcps/vmcp_inline_incoming_auth.yaml @@ -120,8 +120,8 @@ metadata: name: inline-auth-vmcp namespace: default spec: - groupRef: - name: my-services + config: + groupRef: my-services # Incoming authentication - inline OIDC configuration incomingAuth: diff --git a/examples/operator/virtual-mcps/vmcp_production_full.yaml b/examples/operator/virtual-mcps/vmcp_production_full.yaml index 8593ded7b2..a977fbfd7b 100644 --- a/examples/operator/virtual-mcps/vmcp_production_full.yaml +++ b/examples/operator/virtual-mcps/vmcp_production_full.yaml @@ -140,8 +140,8 @@ metadata: environment: production spec: # Reference to the MCPGroup containing backend MCPServers - groupRef: - name: production-services + config: + groupRef: production-services # Incoming authentication (client -> vMCP) # Using OIDC for secure authentication diff --git a/examples/operator/virtual-mcps/vmcp_simple_discovered.yaml b/examples/operator/virtual-mcps/vmcp_simple_discovered.yaml index ad9bab803b..cc9951a2e4 100644 --- a/examples/operator/virtual-mcps/vmcp_simple_discovered.yaml +++ b/examples/operator/virtual-mcps/vmcp_simple_discovered.yaml @@ -53,8 +53,8 @@ metadata: namespace: default spec: # Reference to the MCPGroup containing backend MCPServers - groupRef: - name: my-services + config: + groupRef: my-services # Incoming authentication (client -> vMCP) # Using anonymous auth for simplicity - replace with OIDC in production diff --git a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/audit-chainsaw-test.yaml b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/audit-chainsaw-test.yaml index 0b2ce27f48..ae24f6f2d7 100644 --- a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/audit-chainsaw-test.yaml +++ b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/audit-chainsaw-test.yaml @@ -40,8 +40,8 @@ spec: name: test-vmcp-audit-enabled namespace: toolhive-system spec: - groupRef: - name: test-audit-group + config: + groupRef: test-audit-group incomingAuth: type: anonymous audit: @@ -88,8 +88,8 @@ spec: name: test-vmcp-no-audit namespace: toolhive-system spec: - groupRef: - name: test-audit-group + config: + groupRef: test-audit-group incomingAuth: type: anonymous - assert: @@ -145,8 +145,8 @@ spec: name: test-vmcp-audit-disabled namespace: toolhive-system spec: - groupRef: - name: test-audit-group + config: + groupRef: test-audit-group incomingAuth: type: anonymous audit: diff --git a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/basic/chainsaw-test.yaml b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/basic/chainsaw-test.yaml index 787448057b..d25ad9464b 100644 --- a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/basic/chainsaw-test.yaml +++ b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/basic/chainsaw-test.yaml @@ -25,8 +25,8 @@ spec: metadata: name: test-vmcp spec: - groupRef: - name: test-group + config: + groupRef: test-group incomingAuth: type: oidc oidcConfig: diff --git a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-controller.yaml b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-controller.yaml index 1f5676fb31..05547e0585 100644 --- a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-controller.yaml +++ b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-controller.yaml @@ -4,8 +4,8 @@ metadata: name: test-vmcp-controller namespace: toolhive-system spec: - groupRef: - name: test-group-controller + config: + groupRef: test-group-controller incomingAuth: # Using anonymous authentication for testing type: anonymous diff --git a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-with-oidc.yaml b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-with-oidc.yaml index c9a853fe3b..6ee6170189 100644 --- a/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-with-oidc.yaml +++ b/test/e2e/chainsaw/operator/single-tenancy/test-scenarios/vmcp/vmcp-with-oidc.yaml @@ -4,8 +4,8 @@ metadata: name: test-vmcp-oidc namespace: toolhive-system spec: - groupRef: - name: test-group-controller + config: + groupRef: test-group-controller incomingAuth: type: oidc oidcConfig: From 6303a20fc457b52e161c1b2eb22c552307eda515 Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Tue, 6 Jan 2026 13:45:04 -0800 Subject: [PATCH 3/4] name must be optional Signed-off-by: Jeremy Drouillard --- .../files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml | 1 - .../templates/toolhive.stacklok.dev_virtualmcpservers.yaml | 1 - pkg/vmcp/config/config.go | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml index 55c23cbcdf..1acbf361fe 100644 --- a/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml +++ b/deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yaml @@ -1103,7 +1103,6 @@ spec: type: object required: - groupRef - - name type: object x-kubernetes-preserve-unknown-fields: true incomingAuth: diff --git a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml index 573b47d0be..ec87016d22 100644 --- a/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml +++ b/deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yaml @@ -1106,7 +1106,6 @@ spec: type: object required: - groupRef - - name type: object x-kubernetes-preserve-unknown-fields: true incomingAuth: diff --git a/pkg/vmcp/config/config.go b/pkg/vmcp/config/config.go index df953ddb66..aeb05dc138 100644 --- a/pkg/vmcp/config/config.go +++ b/pkg/vmcp/config/config.go @@ -69,7 +69,8 @@ func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { // +kubebuilder:validation:Type=object type Config struct { // Name is the virtual MCP server name. - Name string `json:"name" yaml:"name"` + // +optional + Name string `json:"name,omitempty" yaml:"name,omitempty"` // Group references an existing MCPGroup that defines backend workloads. // In Kubernetes, the referenced MCPGroup must exist in the same namespace. From a77563da0a15275827b6679e31dbd3c6266035cb Mon Sep 17 00:00:00 2001 From: Jeremy Drouillard Date: Wed, 7 Jan 2026 14:46:56 -0800 Subject: [PATCH 4/4] chart lint and fix test Signed-off-by: Jeremy Drouillard --- deploy/charts/operator-crds/Chart.yaml | 2 +- deploy/charts/operator-crds/README.md | 2 +- test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/deploy/charts/operator-crds/Chart.yaml b/deploy/charts/operator-crds/Chart.yaml index 3097480de4..84f6159538 100644 --- a/deploy/charts/operator-crds/Chart.yaml +++ b/deploy/charts/operator-crds/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: toolhive-operator-crds description: A Helm chart for installing the ToolHive Operator CRDs into Kubernetes. type: application -version: 0.0.89 +version: 0.0.90 appVersion: "0.0.1" diff --git a/deploy/charts/operator-crds/README.md b/deploy/charts/operator-crds/README.md index b1af8ad208..9e231efa51 100644 --- a/deploy/charts/operator-crds/README.md +++ b/deploy/charts/operator-crds/README.md @@ -1,6 +1,6 @@ # ToolHive Operator CRDs Helm Chart -![Version: 0.0.89](https://img.shields.io/badge/Version-0.0.89-informational?style=flat-square) +![Version: 0.0.90](https://img.shields.io/badge/Version-0.0.90-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) A Helm chart for installing the ToolHive Operator CRDs into Kubernetes. diff --git a/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go b/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go index 2211ed5542..1842cf5c67 100644 --- a/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go +++ b/test/e2e/thv-operator/virtualmcp/virtualmcp_lifecycle_test.go @@ -655,9 +655,7 @@ var _ = Describe("VirtualMCPServer K8s Manager Infrastructure", Ordered, func() Namespace: testNamespace, }, Spec: mcpv1alpha1.VirtualMCPServerSpec{ - GroupRef: mcpv1alpha1.GroupRef{ - Name: mcpGroupName, - }, + Config: vmcpconfig.Config{Group: mcpGroupName}, IncomingAuth: &mcpv1alpha1.IncomingAuthConfig{ Type: "anonymous", },