@@ -93,18 +93,85 @@ func (c *MCPRouteController) syncMCPRoute(ctx context.Context, mcpRoute *aigv1a1
9393 if err := c .ensureMCPProxyBackend (ctx , mcpRoute ); err != nil {
9494 return fmt .Errorf ("failed to ensure MCP proxy Backend: %w" , err )
9595 }
96-
97- // Check if the HTTPRoute exists.
98- httpRouteName := internalapi .MCPHTTPRoutePrefix + mcpRoute .Name
9996 c .logger .Info ("Syncing MCPRoute" , "namespace" , mcpRoute .Namespace , "name" , mcpRoute .Name )
100- var httpRoute gwapiv1.HTTPRoute
101- err := c .client .Get (ctx , client.ObjectKey {Name : httpRouteName , Namespace : mcpRoute .Namespace }, & httpRoute )
102- existingRoute := err == nil
97+
98+ // First, we create or update the main HTTPRoute that routes to the MCP proxy.
99+ // The main HTTPRoutes will not be "moved" into the MCP Backend listener in the extension server.
100+ mainHTTPRouteName := internalapi .MCPMainHTTPRoutePrefix + mcpRoute .Name
101+ mainHTTPRoute , existing , err := c .getOrNewHTTPRouteRoute (ctx , mcpRoute , mainHTTPRouteName )
102+ if err != nil {
103+ return fmt .Errorf ("failed to get or create HTTPRoute: %w" , err )
104+ }
105+ if err = c .newMainHTTPRoute (mainHTTPRoute , mcpRoute ); err != nil {
106+ return fmt .Errorf ("failed to construct a new HTTPRoute: %w" , err )
107+ }
108+
109+ // Create or Update the main HTTPRoute.
110+ if err = c .createOrUpdateHTTPRoute (ctx , mainHTTPRoute , existing ); err != nil {
111+ return fmt .Errorf ("failed to create or update HTTPRoute: %w" , err )
112+ }
113+
114+ // Then, build HTTPRoute for each backend in the MCPRoute to avoid the hard limit of 16 Rules per HTTPRoute.
115+ // The route here will be moved to the backend listener in the extension server behind the MCP Proxy.
116+ //
117+ // Each backend will have its own rule that matches the internalapi.MCPBackendHeader set by the MCP proxy.
118+ // This allows the MCP proxy to route requests to the correct backend based on the header.
119+ for i := range mcpRoute .Spec .BackendRefs {
120+ ref := & mcpRoute .Spec .BackendRefs [i ]
121+ name := mcpPerBackendRefHTTPRouteName (mcpRoute .Name , ref .Name )
122+ var httpRoute * gwapiv1.HTTPRoute
123+ httpRoute , existing , err = c .getOrNewHTTPRouteRoute (ctx , mcpRoute , name )
124+ if err != nil {
125+ return fmt .Errorf ("failed to get or create HTTPRoute: %w" , err )
126+ }
127+ if err = c .newPerBackendRefHTTPRoute (ctx , httpRoute , mcpRoute , ref ); err != nil {
128+ return fmt .Errorf ("failed to construct a new HTTPRoute for backend %s: %w" , ref .Name , err )
129+ }
130+ if err = c .createOrUpdateHTTPRoute (ctx , httpRoute , existing ); err != nil {
131+ return fmt .Errorf ("failed to create or update HTTPRoute for backend %s: %w" , ref .Name , err )
132+ }
133+ }
134+
135+ // Reconciles MCPRouteSecurityPolicy and creates/updates its associated envoy gateway resources.
136+ if err = c .syncMCPRouteSecurityPolicy (ctx , mcpRoute , mainHTTPRouteName ); err != nil {
137+ return fmt .Errorf ("failed to sync MCP route security policy: %w" , err )
138+ }
139+
140+ err = c .syncGateways (ctx , mcpRoute )
141+ if err != nil {
142+ return fmt .Errorf ("failed to sync gw pods: %w" , err )
143+ }
144+ return nil
145+ }
146+
147+ func mcpPerBackendRefHTTPRouteName (mcpRouteName string , backendName gwapiv1.ObjectName ) string {
148+ return fmt .Sprintf ("%s%s-%s" , internalapi .MCPPerBackendRefHTTPRoutePrefix , mcpRouteName , backendName )
149+ }
150+
151+ func (c * MCPRouteController ) createOrUpdateHTTPRoute (ctx context.Context , httpRoute * gwapiv1.HTTPRoute , update bool ) error {
152+ if update {
153+ c .logger .Info ("Updating HTTPRoute" , "namespace" , httpRoute .Namespace , "name" , httpRoute .Name )
154+ if err := c .client .Update (ctx , httpRoute ); err != nil {
155+ return fmt .Errorf ("failed to update HTTPRoute: %w" , err )
156+ }
157+ } else {
158+ c .logger .Info ("Creating HTTPRoute" , "namespace" , httpRoute .Namespace , "name" , httpRoute .Name )
159+ if err := c .client .Create (ctx , httpRoute ); err != nil {
160+ return fmt .Errorf ("failed to create HTTPRoute: %w" , err )
161+ }
162+ }
163+ return nil
164+ }
165+
166+ func (c * MCPRouteController ) getOrNewHTTPRouteRoute (ctx context.Context , mcpRoute * aigv1a1.MCPRoute , routeName string ) (* gwapiv1.HTTPRoute , bool , error ) {
167+ httpRoute := & gwapiv1.HTTPRoute {}
168+ err := c .client .Get (ctx , client.ObjectKey {Name : routeName , Namespace : mcpRoute .Namespace }, httpRoute )
169+ existing := err == nil
103170 if apierrors .IsNotFound (err ) {
104171 // This means that this MCPRoute is a new one.
105- httpRoute = gwapiv1.HTTPRoute {
172+ httpRoute = & gwapiv1.HTTPRoute {
106173 ObjectMeta : metav1.ObjectMeta {
107- Name : httpRouteName ,
174+ Name : routeName ,
108175 Namespace : mcpRoute .Namespace ,
109176 Labels : make (map [string ]string ),
110177 Annotations : make (map [string ]string ),
@@ -121,44 +188,17 @@ func (c *MCPRouteController) syncMCPRoute(ctx context.Context, mcpRoute *aigv1a1
121188 for k , v := range mcpRoute .Annotations {
122189 httpRoute .Annotations [k ] = v
123190 }
124- if err = ctrlutil .SetControllerReference (mcpRoute , & httpRoute , c .client .Scheme ()); err != nil {
125- panic ( fmt .Errorf ("BUG: failed to set controller reference for HTTPRoute: %w" , err ) )
191+ if err = ctrlutil .SetControllerReference (mcpRoute , httpRoute , c .client .Scheme ()); err != nil {
192+ return nil , false , fmt .Errorf ("failed to set controller reference for HTTPRoute: %w" , err )
126193 }
127194 } else if err != nil {
128- return fmt .Errorf ("failed to get HTTPRoute: %w" , err )
129- }
130-
131- // Update the HTTPRoute with the new MCPRoute.
132- if err = c .newHTTPRoute (ctx , & httpRoute , mcpRoute ); err != nil {
133- return fmt .Errorf ("failed to construct a new HTTPRoute: %w" , err )
134- }
135-
136- if existingRoute {
137- c .logger .Info ("Updating HTTPRoute" , "namespace" , httpRoute .Namespace , "name" , httpRoute .Name )
138- if err = c .client .Update (ctx , & httpRoute ); err != nil {
139- return fmt .Errorf ("failed to update HTTPRoute: %w" , err )
140- }
141- } else {
142- c .logger .Info ("Creating HTTPRoute" , "namespace" , httpRoute .Namespace , "name" , httpRoute .Name )
143- if err = c .client .Create (ctx , & httpRoute ); err != nil {
144- return fmt .Errorf ("failed to create HTTPRoute: %w" , err )
145- }
195+ return nil , false , fmt .Errorf ("failed to get HTTPRoute: %w" , err )
146196 }
147-
148- // Reconciles MCPRouteSecurityPolicy and creates/updates its associated envoy gateway resources.
149- if err = c .syncMCPRouteSecurityPolicy (ctx , mcpRoute , httpRouteName ); err != nil {
150- return fmt .Errorf ("failed to sync MCP route security policy: %w" , err )
151- }
152-
153- err = c .syncGateways (ctx , mcpRoute )
154- if err != nil {
155- return fmt .Errorf ("failed to sync gw pods: %w" , err )
156- }
157- return nil
197+ return httpRoute , existing , nil
158198}
159199
160- // newHTTPRoute updates the HTTPRoute with the new MCPRoute.
161- func (c * MCPRouteController ) newHTTPRoute ( ctx context. Context , dst * gwapiv1.HTTPRoute , mcpRoute * aigv1a1.MCPRoute ) error {
200+ // newMainHTTPRoute updates the main HTTPRoute with the MCPRoute.
201+ func (c * MCPRouteController ) newMainHTTPRoute ( dst * gwapiv1.HTTPRoute , mcpRoute * aigv1a1.MCPRoute ) error {
162202 // This routes incoming MCP client requests to the MCP proxy in the ext proc.
163203 servingPath := ptr .Deref (mcpRoute .Spec .Path , defaultMCPPath )
164204 rules := []gwapiv1.HTTPRouteRule {{
@@ -325,24 +365,44 @@ func (c *MCPRouteController) newHTTPRoute(ctx context.Context, dst *gwapiv1.HTTP
325365 rules = append (rules , authServerSuffixRule )
326366 }
327367 }
368+ dst .Spec .Rules = rules
328369
329- // Build HTTPRouteRules for each backend in the MCPRoute.
330- // Each backend will have its own rule that matches the internalapi.MCPBackendHeader set by the MCP proxy.
331- // This allows the MCP proxy to route requests to the correct backend based on the header.
332- for i := range mcpRoute .Spec .BackendRefs {
333- ref := & mcpRoute .Spec .BackendRefs [i ]
334- if ns := ref .Namespace ; ns != nil && * ns != gwapiv1 .Namespace (mcpRoute .Namespace ) {
335- // TODO: do this in a CEL or webhook validation or start supporting cross-namespace references with ReferenceGrant.
336- return fmt .Errorf ("cross-namespace backend reference is not supported: backend %s/%s in MCPRoute %s/%s" ,
337- * ns , ref .Name , mcpRoute .Namespace , mcpRoute .Name )
338- }
339- mcpBackendToHTTPRouteRule , err := c .mcpBackendRefToHTTPRouteRule (ctx , mcpRoute , ref )
340- if err != nil {
341- return fmt .Errorf ("failed to convert MCPRouteRule to HTTPRouteRule: %w" , err )
342- }
343- rules = append (rules , mcpBackendToHTTPRouteRule )
370+ // Initialize labels and annotations maps if they don't exist.
371+ if dst .Labels == nil {
372+ dst .Labels = make (map [string ]string )
344373 }
345- dst .Spec .Rules = rules
374+ if dst .Annotations == nil {
375+ dst .Annotations = make (map [string ]string )
376+ }
377+
378+ // Copy labels from MCPRoute to HTTPRoute.
379+ for k , v := range mcpRoute .Labels {
380+ dst .Labels [k ] = v
381+ }
382+
383+ // Copy non-controller annotations from MCPRoute to HTTPRoute.
384+ for k , v := range mcpRoute .Annotations {
385+ dst .Annotations [k ] = v
386+ }
387+
388+ // Mark this HTTPRoute as generated by the MCP Gateway controller with the hash of the backend refs so that
389+ // this will invoke an extension server update.
390+ dst .Spec .ParentRefs = mcpRoute .Spec .ParentRefs
391+ return nil
392+ }
393+
394+ // newPerBackendRefHTTPRoute creates an HTTPRoute for each backend reference in the MCPRoute.
395+ func (c * MCPRouteController ) newPerBackendRefHTTPRoute (ctx context.Context , dst * gwapiv1.HTTPRoute , mcpRoute * aigv1a1.MCPRoute , ref * aigv1a1.MCPRouteBackendRef ) error {
396+ if ns := ref .Namespace ; ns != nil && * ns != gwapiv1 .Namespace (mcpRoute .Namespace ) {
397+ // TODO: do this in a CEL or webhook validation or start supporting cross-namespace references with ReferenceGrant.
398+ return fmt .Errorf ("cross-namespace backend reference is not supported: backend %s/%s in MCPRoute %s/%s" ,
399+ * ns , ref .Name , mcpRoute .Namespace , mcpRoute .Name )
400+ }
401+ mcpBackendToHTTPRouteRule , err := c .mcpBackendRefToHTTPRouteRule (ctx , mcpRoute , ref )
402+ if err != nil {
403+ return fmt .Errorf ("failed to convert MCPRouteRule to HTTPRouteRule: %w" , err )
404+ }
405+ dst .Spec .Rules = []gwapiv1.HTTPRouteRule {mcpBackendToHTTPRouteRule }
346406
347407 // Initialize labels and annotations maps if they don't exist.
348408 if dst .Labels == nil {
@@ -457,7 +517,7 @@ func mcpProxyBackendName(mcpRoute *aigv1a1.MCPRoute) string {
457517}
458518
459519func mcpBackendRefFilterName (mcpRoute * aigv1a1.MCPRoute , backendName gwapiv1.ObjectName ) string {
460- return fmt .Sprintf ("%s%s-%s" , internalapi .MCPBackendFilterPrefix , mcpRoute .Name , backendName )
520+ return fmt .Sprintf ("%s%s-%s" , internalapi .MCPPerBackendHTTPRouteFilterPrefix , mcpRoute .Name , backendName )
461521}
462522
463523// mcpBackendRefToHTTPRouteRule creates an HTTPRouteRule for the given MCPRouteBackendRef.
0 commit comments