Skip to content

Commit aef3560

Browse files
ChrisJBurnsMatteoManzoni
authored andcommitted
Wire MCPOIDCConfig into MCPRemoteProxy controller (stacklok#4509)
1 parent 95c60b9 commit aef3560

16 files changed

Lines changed: 1254 additions & 76 deletions

cmd/thv-operator/api/v1alpha1/mcpremoteproxy_types.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ type HeaderFromSecret struct {
3434
}
3535

3636
// MCPRemoteProxySpec defines the desired state of MCPRemoteProxy
37+
//
38+
// +kubebuilder:validation:XValidation:rule="!(has(self.oidcConfig) && has(self.oidcConfigRef))",message="oidcConfig and oidcConfigRef are mutually exclusive; use oidcConfigRef to reference a shared MCPOIDCConfig"
39+
//
40+
//nolint:lll // CEL validation rules exceed line length limit
3741
type MCPRemoteProxySpec struct {
3842
// RemoteURL is the URL of the remote MCP server to proxy
3943
// +kubebuilder:validation:Required
@@ -51,10 +55,18 @@ type MCPRemoteProxySpec struct {
5155
// +kubebuilder:default=streamable-http
5256
Transport string `json:"transport,omitempty"`
5357

54-
// OIDCConfig defines OIDC authentication configuration for the proxy
55-
// This validates incoming tokens from clients. Required for proxy mode.
56-
// +kubebuilder:validation:Required
57-
OIDCConfig OIDCConfigRef `json:"oidcConfig"`
58+
// OIDCConfig defines OIDC authentication configuration for the proxy.
59+
// Deprecated: Use OIDCConfigRef to reference a shared MCPOIDCConfig resource instead.
60+
// This field will be removed in v1beta1. OIDCConfig and OIDCConfigRef are mutually exclusive.
61+
// +optional
62+
OIDCConfig *OIDCConfigRef `json:"oidcConfig,omitempty"`
63+
64+
// OIDCConfigRef references a shared MCPOIDCConfig resource for OIDC authentication.
65+
// The referenced MCPOIDCConfig must exist in the same namespace as this MCPRemoteProxy.
66+
// Per-server overrides (audience, scopes) are specified here; shared provider config
67+
// lives in the MCPOIDCConfig resource.
68+
// +optional
69+
OIDCConfigRef *MCPOIDCConfigReference `json:"oidcConfigRef,omitempty"`
5870

5971
// ExternalAuthConfigRef references a MCPExternalAuthConfig resource for token exchange.
6072
// When specified, the proxy will exchange validated incoming tokens for remote service tokens.
@@ -156,6 +168,10 @@ type MCPRemoteProxyStatus struct {
156168
// +optional
157169
ExternalAuthConfigHash string `json:"externalAuthConfigHash,omitempty"`
158170

171+
// OIDCConfigHash is the hash of the referenced MCPOIDCConfig spec for change detection
172+
// +optional
173+
OIDCConfigHash string `json:"oidcConfigHash,omitempty"`
174+
159175
// Message provides additional information about the current phase
160176
// +optional
161177
Message string `json:"message,omitempty"`
@@ -325,7 +341,7 @@ func (m *MCPRemoteProxy) GetNamespace() string {
325341

326342
// GetOIDCConfig returns the OIDC configuration reference
327343
func (m *MCPRemoteProxy) GetOIDCConfig() *OIDCConfigRef {
328-
return &m.Spec.OIDCConfig
344+
return m.Spec.OIDCConfig
329345
}
330346

331347
// GetProxyPort returns the proxy port of the MCPRemoteProxy

cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/thv-operator/controllers/mcpoidcconfig_controller.go

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type MCPOIDCConfigReconciler struct {
4646
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpoidcconfigs/status,verbs=get;update;patch
4747
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpoidcconfigs/finalizers,verbs=update
4848
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=virtualmcpservers,verbs=get;list;watch
49+
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpremoteproxies,verbs=get;list;watch
4950

5051
// Reconcile is part of the main kubernetes reconciliation loop which aims to
5152
// move the current state of the cluster closer to the desired state.
@@ -199,7 +200,7 @@ func (r *MCPOIDCConfigReconciler) handleDeletion(
199200
return ctrl.Result{}, nil
200201
}
201202

202-
// findReferencingWorkloads returns the workload resources (MCPServer and VirtualMCPServer)
203+
// findReferencingWorkloads returns the workload resources (MCPServer, VirtualMCPServer, and MCPRemoteProxy)
203204
// that reference this MCPOIDCConfig via their OIDCConfigRef field.
204205
func (r *MCPOIDCConfigReconciler) findReferencingWorkloads(
205206
ctx context.Context,
@@ -230,12 +231,23 @@ func (r *MCPOIDCConfigReconciler) findReferencingWorkloads(
230231
}
231232
}
232233

234+
// Check MCPRemoteProxies
235+
proxyList := &mcpv1alpha1.MCPRemoteProxyList{}
236+
if err := r.List(ctx, proxyList, client.InNamespace(oidcConfig.Namespace)); err != nil {
237+
return nil, fmt.Errorf("failed to list MCPRemoteProxies: %w", err)
238+
}
239+
for _, proxy := range proxyList.Items {
240+
if proxy.Spec.OIDCConfigRef != nil && proxy.Spec.OIDCConfigRef.Name == oidcConfig.Name {
241+
refs = append(refs, mcpv1alpha1.WorkloadReference{Kind: mcpv1alpha1.WorkloadKindMCPRemoteProxy, Name: proxy.Name})
242+
}
243+
}
244+
233245
ctrlutil.SortWorkloadRefs(refs)
234246
return refs, nil
235247
}
236248

237249
// SetupWithManager sets up the controller with the Manager.
238-
// Watches MCPServer and VirtualMCPServer changes to maintain accurate ReferencingWorkloads status.
250+
// Watches MCPServer, VirtualMCPServer, and MCPRemoteProxy changes to maintain accurate ReferencingWorkloads status.
239251
func (r *MCPOIDCConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
240252
// Watch MCPServer changes to update ReferencingWorkloads on referenced MCPOIDCConfigs.
241253
// This handler enqueues both the currently-referenced MCPOIDCConfig AND any
@@ -293,6 +305,10 @@ func (r *MCPOIDCConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
293305
&mcpv1alpha1.VirtualMCPServer{},
294306
handler.EnqueueRequestsFromMapFunc(r.mapVirtualMCPServerToOIDCConfig),
295307
).
308+
Watches(
309+
&mcpv1alpha1.MCPRemoteProxy{},
310+
handler.EnqueueRequestsFromMapFunc(r.mapMCPRemoteProxyToOIDCConfig),
311+
).
296312
Complete(r)
297313
}
298314

@@ -342,3 +358,50 @@ func (r *MCPOIDCConfigReconciler) mapVirtualMCPServerToOIDCConfig(
342358

343359
return requests
344360
}
361+
362+
// mapMCPRemoteProxyToOIDCConfig maps MCPRemoteProxy changes to MCPOIDCConfig reconciliation requests.
363+
// Enqueues both the currently-referenced config and any config that still lists this
364+
// MCPRemoteProxy in ReferencingWorkloads (handles ref-removal / deletion).
365+
func (r *MCPOIDCConfigReconciler) mapMCPRemoteProxyToOIDCConfig(
366+
ctx context.Context, obj client.Object,
367+
) []reconcile.Request {
368+
proxy, ok := obj.(*mcpv1alpha1.MCPRemoteProxy)
369+
if !ok {
370+
return nil
371+
}
372+
373+
seen := make(map[types.NamespacedName]struct{})
374+
var requests []reconcile.Request
375+
376+
// Enqueue the currently-referenced MCPOIDCConfig (if any)
377+
if proxy.Spec.OIDCConfigRef != nil {
378+
nn := types.NamespacedName{
379+
Name: proxy.Spec.OIDCConfigRef.Name,
380+
Namespace: proxy.Namespace,
381+
}
382+
seen[nn] = struct{}{}
383+
requests = append(requests, reconcile.Request{NamespacedName: nn})
384+
}
385+
386+
// Also enqueue any MCPOIDCConfig that still lists this MCPRemoteProxy in
387+
// ReferencingWorkloads — handles ref-removal and deletion cases.
388+
oidcConfigList := &mcpv1alpha1.MCPOIDCConfigList{}
389+
if err := r.List(ctx, oidcConfigList, client.InNamespace(proxy.Namespace)); err != nil {
390+
log.FromContext(ctx).Error(err, "Failed to list MCPOIDCConfigs for MCPRemoteProxy watch")
391+
return requests
392+
}
393+
for _, cfg := range oidcConfigList.Items {
394+
nn := types.NamespacedName{Name: cfg.Name, Namespace: cfg.Namespace}
395+
if _, already := seen[nn]; already {
396+
continue
397+
}
398+
for _, ref := range cfg.Status.ReferencingWorkloads {
399+
if ref.Kind == mcpv1alpha1.WorkloadKindMCPRemoteProxy && ref.Name == proxy.Name {
400+
requests = append(requests, reconcile.Request{NamespacedName: nn})
401+
break
402+
}
403+
}
404+
}
405+
406+
return requests
407+
}

0 commit comments

Comments
 (0)