@@ -98,6 +98,15 @@ func (r *ToolConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
9898 return r .handleConfigHashChange (ctx , toolConfig , configHash )
9999 }
100100
101+ // Refresh ReferencingWorkloads list
102+ referencingWorkloads , err := r .findReferencingWorkloads (ctx , toolConfig )
103+ if err != nil {
104+ logger .Error (err , "Failed to find referencing workloads" )
105+ } else if ! slices .Equal (toolConfig .Status .ReferencingWorkloads , referencingWorkloads ) {
106+ toolConfig .Status .ReferencingWorkloads = referencingWorkloads
107+ conditionChanged = true
108+ }
109+
101110 // Update condition if it changed (even without hash change)
102111 if conditionChanged {
103112 if err := r .Status ().Update (ctx , toolConfig ); err != nil {
@@ -106,9 +115,7 @@ func (r *ToolConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
106115 }
107116 }
108117
109- // Even when hash hasn't changed, update referencing servers list.
110- // This ensures ReferencingServers is updated when MCPServers are created or deleted.
111- return r .updateReferencingServers (ctx , toolConfig )
118+ return ctrl.Result {}, nil
112119}
113120
114121// handleConfigHashChange handles the logic when the config hash changes
@@ -134,13 +141,12 @@ func (r *ToolConfigReconciler) handleConfigHashChange(
134141 toolConfig .Status .ConfigHash = configHash
135142 toolConfig .Status .ObservedGeneration = toolConfig .Generation
136143
137- // Update the status with the list of referencing servers
138- serverNames := make ([]string , 0 , len (referencingServers ))
144+ // Update the status with the list of referencing workloads
145+ refs := make ([]mcpv1alpha1. WorkloadReference , 0 , len (referencingServers ))
139146 for _ , server := range referencingServers {
140- serverNames = append (serverNames , server .Name )
147+ refs = append (refs , mcpv1alpha1. WorkloadReference { Kind : "MCPServer" , Name : server .Name } )
141148 }
142- slices .Sort (serverNames )
143- toolConfig .Status .ReferencingServers = serverNames
149+ toolConfig .Status .ReferencingWorkloads = refs
144150
145151 // Update the MCPToolConfig status
146152 if err := r .Status ().Update (ctx , toolConfig ); err != nil {
@@ -166,46 +172,6 @@ func (r *ToolConfigReconciler) handleConfigHashChange(
166172 return ctrl.Result {}, nil
167173}
168174
169- // updateReferencingServers finds referencing MCPServers and updates the status if the list changed
170- func (r * ToolConfigReconciler ) updateReferencingServers (
171- ctx context.Context ,
172- toolConfig * mcpv1alpha1.MCPToolConfig ,
173- ) (ctrl.Result , error ) {
174- referencingServers , err := r .findReferencingMCPServers (ctx , toolConfig )
175- if err != nil {
176- logger := log .FromContext (ctx )
177- logger .Error (err , "Failed to find referencing MCPServers" )
178- meta .SetStatusCondition (& toolConfig .Status .Conditions , metav1.Condition {
179- Type : mcpv1alpha1 .ConditionToolConfigValid ,
180- Status : metav1 .ConditionFalse ,
181- Reason : mcpv1alpha1 .ConditionReasonToolConfigValidationFailed ,
182- Message : "Failed to find referencing MCPServers" ,
183- ObservedGeneration : toolConfig .Generation ,
184- })
185- if updateErr := r .Status ().Update (ctx , toolConfig ); updateErr != nil {
186- logger .Error (updateErr , "Failed to update MCPToolConfig status after error" )
187- }
188- return ctrl.Result {}, fmt .Errorf ("failed to find referencing MCPServers: %w" , err )
189- }
190-
191- serverNames := make ([]string , 0 , len (referencingServers ))
192- for _ , server := range referencingServers {
193- serverNames = append (serverNames , server .Name )
194- }
195- slices .Sort (serverNames )
196-
197- if ! slices .Equal (toolConfig .Status .ReferencingServers , serverNames ) {
198- toolConfig .Status .ReferencingServers = serverNames
199- if err := r .Status ().Update (ctx , toolConfig ); err != nil {
200- logger := log .FromContext (ctx )
201- logger .Error (err , "Failed to update MCPToolConfig status" )
202- return ctrl.Result {}, err
203- }
204- }
205-
206- return ctrl.Result {}, nil
207- }
208-
209175// calculateConfigHash calculates a hash of the MCPToolConfig spec using Kubernetes utilities
210176func (* ToolConfigReconciler ) calculateConfigHash (spec mcpv1alpha1.MCPToolConfigSpec ) string {
211177 return ctrlutil .CalculateConfigHash (spec )
@@ -216,31 +182,32 @@ func (r *ToolConfigReconciler) handleDeletion(ctx context.Context, toolConfig *m
216182 logger := log .FromContext (ctx )
217183
218184 if controllerutil .ContainsFinalizer (toolConfig , ToolConfigFinalizerName ) {
219- // Check if any MCPServers are still referencing this MCPToolConfig
220- referencingServers , err := r .findReferencingMCPServers (ctx , toolConfig )
185+ // Check if any workloads still reference this MCPToolConfig
186+ referencingWorkloads , err := r .findReferencingWorkloads (ctx , toolConfig )
221187 if err != nil {
222- logger .Error (err , "Failed to find referencing MCPServers during deletion" )
188+ logger .Error (err , "Failed to check referencing workloads during deletion" )
223189 return ctrl.Result {}, err
224190 }
225191
226- if len (referencingServers ) > 0 {
227- // Cannot delete - still referenced
228- serverNames := make ([]string , 0 , len (referencingServers ))
229- for _ , server := range referencingServers {
230- serverNames = append (serverNames , server .Name )
192+ if len (referencingWorkloads ) > 0 {
193+ logger .Info ("MCPToolConfig is still referenced by workloads, blocking deletion" ,
194+ "toolconfig" , toolConfig .Name ,
195+ "referencingWorkloads" , referencingWorkloads )
196+
197+ meta .SetStatusCondition (& toolConfig .Status .Conditions , metav1.Condition {
198+ Type : "DeletionBlocked" ,
199+ Status : metav1 .ConditionTrue ,
200+ Reason : "ReferencedByWorkloads" ,
201+ Message : fmt .Sprintf ("Cannot delete: referenced by workloads: %v" , referencingWorkloads ),
202+ ObservedGeneration : toolConfig .Generation ,
203+ })
204+ toolConfig .Status .ReferencingWorkloads = referencingWorkloads
205+ if updateErr := r .Status ().Update (ctx , toolConfig ); updateErr != nil {
206+ logger .Error (updateErr , "Failed to update status during deletion block" )
231207 }
232- logger .Info ("Cannot delete MCPToolConfig - still referenced by MCPServers" ,
233- "toolconfig" , toolConfig .Name , "referencingServers" , serverNames )
234208
235- // Update status to show it's still referenced
236- toolConfig .Status .ReferencingServers = serverNames
237- if err := r .Status ().Update (ctx , toolConfig ); err != nil {
238- logger .Error (err , "Failed to update MCPToolConfig status during deletion" )
239- }
240-
241- // Return an error to prevent deletion
242- return ctrl.Result {}, fmt .Errorf ("MCPToolConfig %s is still referenced by MCPServers: %v" ,
243- toolConfig .Name , serverNames )
209+ // Requeue to check again later
210+ return ctrl.Result {RequeueAfter : 30 * time .Second }, nil
244211 }
245212
246213 // No references, safe to remove finalizer and allow deletion
@@ -255,7 +222,28 @@ func (r *ToolConfigReconciler) handleDeletion(ctx context.Context, toolConfig *m
255222 return ctrl.Result {}, nil
256223}
257224
258- // findReferencingMCPServers finds all MCPServers that reference the given MCPToolConfig
225+ // findReferencingWorkloads returns the workload resources (MCPServer)
226+ // that reference this MCPToolConfig via their ToolConfigRef field.
227+ func (r * ToolConfigReconciler ) findReferencingWorkloads (
228+ ctx context.Context ,
229+ toolConfig * mcpv1alpha1.MCPToolConfig ,
230+ ) ([]mcpv1alpha1.WorkloadReference , error ) {
231+ mcpServerList := & mcpv1alpha1.MCPServerList {}
232+ if err := r .List (ctx , mcpServerList , client .InNamespace (toolConfig .Namespace )); err != nil {
233+ return nil , fmt .Errorf ("failed to list MCPServers: %w" , err )
234+ }
235+
236+ var refs []mcpv1alpha1.WorkloadReference
237+ for _ , server := range mcpServerList .Items {
238+ if server .Spec .ToolConfigRef != nil && server .Spec .ToolConfigRef .Name == toolConfig .Name {
239+ refs = append (refs , mcpv1alpha1.WorkloadReference {Kind : "MCPServer" , Name : server .Name })
240+ }
241+ }
242+ return refs , nil
243+ }
244+
245+ // findReferencingMCPServers finds all MCPServers that reference the given MCPToolConfig.
246+ // Returns the full MCPServer objects, used by handleConfigHashChange to update server annotations.
259247func (r * ToolConfigReconciler ) findReferencingMCPServers (
260248 ctx context.Context ,
261249 toolConfig * mcpv1alpha1.MCPToolConfig ,
@@ -270,33 +258,59 @@ func (r *ToolConfigReconciler) findReferencingMCPServers(
270258}
271259
272260// SetupWithManager sets up the controller with the Manager.
261+ // Watches MCPServer changes to maintain accurate ReferencingWorkloads status.
273262func (r * ToolConfigReconciler ) SetupWithManager (mgr ctrl.Manager ) error {
274- // Create a handler that maps MCPServer changes to MCPToolConfig reconciliation requests.
275- // When an MCPServer is created/updated/deleted, we need to reconcile the MCPToolConfig
276- // it references so that the ReferencingServers status field stays up to date.
263+ // Watch MCPServer changes to update ReferencingWorkloads on referenced MCPToolConfigs.
264+ // This handler enqueues both the currently-referenced MCPToolConfig AND any
265+ // MCPToolConfig that still lists this server in ReferencingWorkloads (covers the
266+ // case where a server removes its toolConfigRef — the previously-referenced
267+ // config needs to reconcile and clean up the stale entry).
277268 toolConfigHandler := handler .EnqueueRequestsFromMapFunc (
278- func (_ context.Context , obj client.Object ) []reconcile.Request {
279- mcpServer , ok := obj .(* mcpv1alpha1.MCPServer )
269+ func (ctx context.Context , obj client.Object ) []reconcile.Request {
270+ server , ok := obj .(* mcpv1alpha1.MCPServer )
280271 if ! ok {
281272 return nil
282273 }
283274
284- if mcpServer .Spec .ToolConfigRef == nil {
285- return nil
275+ seen := make (map [types.NamespacedName ]struct {})
276+ var requests []reconcile.Request
277+
278+ // Enqueue the currently-referenced MCPToolConfig (if any)
279+ if server .Spec .ToolConfigRef != nil {
280+ nn := types.NamespacedName {
281+ Name : server .Spec .ToolConfigRef .Name ,
282+ Namespace : server .Namespace ,
283+ }
284+ seen [nn ] = struct {}{}
285+ requests = append (requests , reconcile.Request {NamespacedName : nn })
286+ }
287+
288+ // Also enqueue any MCPToolConfig that still lists this server in
289+ // ReferencingWorkloads — handles ref-removal and server-deletion cases.
290+ toolConfigList := & mcpv1alpha1.MCPToolConfigList {}
291+ if err := r .List (ctx , toolConfigList , client .InNamespace (server .Namespace )); err != nil {
292+ log .FromContext (ctx ).Error (err , "Failed to list MCPToolConfigs for MCPServer watch" )
293+ return requests
294+ }
295+ for _ , cfg := range toolConfigList .Items {
296+ nn := types.NamespacedName {Name : cfg .Name , Namespace : cfg .Namespace }
297+ if _ , already := seen [nn ]; already {
298+ continue
299+ }
300+ for _ , ref := range cfg .Status .ReferencingWorkloads {
301+ if ref .Kind == "MCPServer" && ref .Name == server .Name {
302+ requests = append (requests , reconcile.Request {NamespacedName : nn })
303+ break
304+ }
305+ }
286306 }
287307
288- return []reconcile.Request {{
289- NamespacedName : types.NamespacedName {
290- Name : mcpServer .Spec .ToolConfigRef .Name ,
291- Namespace : mcpServer .Namespace ,
292- },
293- }}
308+ return requests
294309 },
295310 )
296311
297312 return ctrl .NewControllerManagedBy (mgr ).
298313 For (& mcpv1alpha1.MCPToolConfig {}).
299- // Watch for MCPServers and reconcile the MCPToolConfig when they change
300314 Watches (& mcpv1alpha1.MCPServer {}, toolConfigHandler ).
301315 Complete (r )
302316}
0 commit comments