@@ -150,8 +150,8 @@ service.
150150
151151:::note
152152
153- Path rewriting syntax varies by Ingress controller. Check your controller's
154- documentation for the correct annotations or resources.
153+ Path rewriting support and syntax varies by Ingress controller. Check your
154+ controller's documentation for the correct annotations or resources.
155155
156156:: :
157157
@@ -175,18 +175,20 @@ spec:
175175 - host: mcp.example.com
176176 http:
177177 paths:
178+ # Fetch MCP server
178179 - path: /fetch
179180 pathType: Prefix
180181 backend:
181182 service:
182183 name: mcp-fetch-proxy
183184 port:
184185 number: 8080
185- - path: /weather
186+ # Another MCP server
187+ - path: /<SERVER_NAME>
186188 pathType: Prefix
187189 backend:
188190 service:
189- name: mcp-weather -proxy
191+ name: mcp-<SERVER_NAME> -proxy
190192 port:
191193 number: 8080
192194---
@@ -200,11 +202,117 @@ spec:
200202 stripPrefix:
201203 prefixes:
202204 - /fetch
203- - /weather
205+ - /<SERVER_NAME>
204206` ` `
205207
206208The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
207- ` https://mcp.example.com/weather/mcp` .
209+ ` https://mcp.example.com/<SERVER_NAME>/mcp` .
210+
211+ </TabItem>
212+ <TabItem value='path-based-auth' label='Path-based routing with OAuth'>
213+
214+ This example is the same as the previous path-based routing example but includes
215+ additional rules in the Ingress to direct the `.well-known` path for each MCP
216+ server to the corresponding backend. This is necessary when using OAuth
217+ authentication since the OAuth flow requires access to the `.well-known`
218+ endpoint for discovery.
219+
220+ First, in the MCPServer spec for each server, ensure the `resourceUrl` property
221+ is set to the full client-facing URL :
222+
223+ ` ` ` yaml title="fetch-server-oauth.yaml"
224+ apiVersion: toolhive.stacklok.dev/v1alpha1
225+ kind: MCPServer
226+ # ...
227+ spec:
228+ oidcConfig:
229+ type: inline
230+ resourceUrl: https://mcp.example.com/<SERVER_NAME>/mcp
231+ inline:
232+ # ... other OIDC config ...
233+ ` ` `
234+
235+ The `inline.audience` value should match the audience expected by your identity
236+ provider, and is likely the same for all servers using the same authorization
237+ server. See [Authentication and authorization](./auth-k8s.mdx) for full OIDC
238+ setup instructions.
239+
240+ Configure the Ingress with additional rules for the `.well-known` paths of each
241+ MCP server that has OAuth enabled :
242+
243+ :::note
244+
245+ Path rewriting support and syntax varies by Ingress controller. Check your
246+ controller's documentation for the correct annotations or resources.
247+
248+ :: :
249+
250+ ` ` ` yaml {28-34,43-49} title="mcp-ingress.yaml"
251+ apiVersion: networking.k8s.io/v1
252+ kind: Ingress
253+ metadata:
254+ name: mcp-servers-ingress
255+ namespace: toolhive-system
256+ annotations:
257+ cert-manager.io/cluster-issuer: 'letsencrypt-prod'
258+ # Traefik example: strip path prefix
259+ traefik.ingress.kubernetes.io/router.middlewares: toolhive-system-strip-mcp-prefix@kubernetescrd
260+ spec:
261+ ingressClassName: traefik
262+ tls:
263+ - hosts:
264+ - mcp.example.com
265+ secretName: mcp-tls
266+ rules:
267+ - host: mcp.example.com
268+ http:
269+ paths:
270+ # Fetch MCP server
271+ - path: /fetch
272+ pathType: Prefix
273+ backend:
274+ service:
275+ name: mcp-fetch-proxy
276+ port:
277+ number: 8080
278+ - path: /.well-known/oauth-protected-resource/fetch/mcp
279+ pathType: Exact
280+ backend:
281+ service:
282+ name: mcp-fetch-proxy
283+ port:
284+ number: 8080
285+ # Another MCP server
286+ - path: /<SERVER_NAME>
287+ pathType: Prefix
288+ backend:
289+ service:
290+ name: mcp-<SERVER_NAME>-proxy
291+ port:
292+ number: 8080
293+ - path: /.well-known/oauth-protected-resource/<SERVER_NAME>/mcp
294+ pathType: Exact
295+ backend:
296+ service:
297+ name: mcp-<SERVER_NAME>-proxy
298+ port:
299+ number: 8080
300+ ---
301+ # Traefik Middleware to strip path prefixes
302+ apiVersion: traefik.io/v1alpha1
303+ kind: Middleware
304+ metadata:
305+ name: strip-mcp-prefix
306+ namespace: toolhive-system
307+ spec:
308+ stripPrefix:
309+ prefixes:
310+ - /fetch
311+ - /<SERVER_NAME>
312+ ` ` `
313+
314+ The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
315+ ` https://mcp.example.com/<SERVER_NAME>/mcp` .
208316
209317</TabItem>
210318</Tabs>
@@ -300,7 +408,7 @@ metadata:
300408 namespace: toolhive-system
301409spec:
302410 parentRefs:
303- - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway if using existing )
411+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway)
304412 # namespace: default # Uncomment if Gateway is in a different namespace
305413 hostnames:
306414 - fetch-mcp.example.com # Change to your domain
@@ -327,11 +435,94 @@ metadata:
327435 namespace: toolhive-system
328436spec:
329437 parentRefs:
330- - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway if using existing)
438+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway)
439+ # namespace: default # Uncomment if Gateway is in a different namespace
440+ hostnames:
441+ - mcp.example.com # Change to your domain
442+ rules:
443+ # Fetch MCP server
444+ - matches:
445+ - path:
446+ type: PathPrefix
447+ value: /fetch
448+ filters:
449+ - type: URLRewrite
450+ urlRewrite:
451+ path:
452+ type: ReplacePrefixMatch
453+ replacePrefixMatch: /
454+ backendRefs:
455+ - name: mcp-fetch-proxy # Format: mcp-<mcpserver-name>-proxy
456+ port: 8080 # This matches the proxyPort
457+ # Another MCP server
458+ - matches:
459+ - path:
460+ type: PathPrefix
461+ value: /<SERVER_NAME>
462+ filters:
463+ - type: URLRewrite
464+ urlRewrite:
465+ path:
466+ type: ReplacePrefixMatch
467+ replacePrefixMatch: /
468+ backendRefs:
469+ - name: mcp-<SERVER_NAME>-proxy
470+ port: 8080
471+ ` ` `
472+
473+ The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
474+ ` https://mcp.example.com/<SERVER_NAME>/mcp` .
475+
476+ The `URLRewrite` filter removes the path prefix (e.g., `/fetch`) before
477+ forwarding requests to the backend service, so the MCP server receives requests
478+ at `/mcp` as expected.
479+
480+ </TabItem>
481+ <TabItem value='path-based-auth' label='Path-based routing with OAuth'>
482+
483+ This example is the same as the previous path-based routing example but includes
484+ additional rules in the HTTPRoute to direct the `.well-known` path for each MCP
485+ server to the corresponding backend. This is necessary when using OAuth
486+ authentication since the OAuth flow requires access to the `.well-known`
487+ endpoint for discovery.
488+
489+ First, in the MCPServer spec for each server, ensure the `resourceUrl` property
490+ is set to the full client-facing URL :
491+
492+ ` ` ` yaml title="fetch-server-oauth.yaml"
493+ apiVersion: toolhive.stacklok.dev/v1alpha1
494+ kind: MCPServer
495+ # ...
496+ spec:
497+ oidcConfig:
498+ type: inline
499+ resourceUrl: https://mcp.example.com/<SERVER_NAME>/mcp
500+ inline:
501+ # ... other OIDC config ...
502+ ` ` `
503+
504+ The `inline.audience` value should match the audience expected by your identity
505+ provider, and is likely the same for all servers using the same authorization
506+ server. See [Authentication and authorization](./auth-k8s.mdx) for full OIDC
507+ setup instructions.
508+
509+ Configure the HTTPRoute with additional rules for the `.well-known` paths of
510+ each MCP server that has OAuth enabled :
511+
512+ ` ` ` yaml {27-33,48-54} title="mcp-routes.yaml"
513+ apiVersion: gateway.networking.k8s.io/v1
514+ kind: HTTPRoute
515+ metadata:
516+ name: mcp-servers-route
517+ namespace: toolhive-system
518+ spec:
519+ parentRefs:
520+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway)
331521 # namespace: default # Uncomment if Gateway is in a different namespace
332522 hostnames:
333523 - mcp.example.com # Change to your domain
334524 rules:
525+ # Fetch MCP server
335526 - matches:
336527 - path:
337528 type: PathPrefix
@@ -345,23 +536,38 @@ spec:
345536 backendRefs:
346537 - name: mcp-fetch-proxy # Format: mcp-<mcpserver-name>-proxy
347538 port: 8080 # This matches the proxyPort
539+ - matches:
540+ - path:
541+ type: Exact
542+ value: /.well-known/oauth-protected-resource/fetch/mcp
543+ backendRefs:
544+ - name: mcp-fetch-proxy
545+ port: 8080
546+ # Another MCP server
348547 - matches:
349548 - path:
350549 type: PathPrefix
351- value: /weather
550+ value: /<SERVER_NAME>
352551 filters:
353552 - type: URLRewrite
354553 urlRewrite:
355554 path:
356555 type: ReplacePrefixMatch
357556 replacePrefixMatch: /
358557 backendRefs:
359- - name: mcp-weather-proxy
558+ - name: mcp-<SERVER_NAME>-proxy
559+ port: 8080
560+ - matches:
561+ - path:
562+ type: Exact
563+ value: /.well-known/oauth-protected-resource/<SERVER_NAME>/mcp
564+ backendRefs:
565+ - name: mcp-<SERVER_NAME>-proxy
360566 port: 8080
361567` ` `
362568
363569The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
364- ` https://mcp.example.com/weather /mcp` .
570+ ` https://mcp.example.com/<SERVER_NAME> /mcp` .
365571
366572The `URLRewrite` filter removes the path prefix (e.g., `/fetch`) before
367573forwarding requests to the backend service, so the MCP server receives requests
@@ -740,27 +946,42 @@ Common causes:
740946</details>
741947
742948<details>
743- <summary>DNS resolution fails </summary>
949+ <summary>Authentication failures </summary>
744950
745- If your domain does not resolve to your cluster :
951+ If OAuth authentication is failing when connecting to your MCP server :
746952
747953` ` ` bash
748- # Check Ingress external IP
749- kubectl get ingress -n toolhive-system fetch-mcp-ingress
954+ # Test if the OAuth metadata endpoint is accessible
955+ # For host-based routing
956+ curl https://fetch-mcp.example.com/.well-known/oauth-protected-resource
750957
751- # Test DNS resolution
752- nslookup fetch- mcp.example.com
958+ # For path-based routing
959+ curl https:// mcp.example.com/.well-known/oauth-protected-resource/fetch/mcp
753960
754- # Or using dig
755- dig fetch-mcp.example.com
961+ # Check proxy logs for authentication errors
962+ kubectl logs -n toolhive-system -l app.kubernetes.io/instance=fetch
963+
964+ # Verify the MCPServer OIDC configuration
965+ kubectl get mcpserver -n toolhive-system fetch -o yaml | grep -A15 oidcConfig
756966` ` `
757967
758- Solutions :
968+ Common causes :
759969
760- - Configure your DNS provider to create an A record pointing to the Ingress
761- external IP
762- - If using a cloud provider load balancer, create a CNAME record instead
763- - Wait for DNS propagation (can take minutes to hours)
970+ - **`.well-known` endpoint not accessible**: When using path-based routing with
971+ OAuth, you must configure your Ingress or HTTPRoute to forward the
972+ ` .well-known` path to the backend. See the "Path-based routing with OAuth" tab
973+ in the Ingress or Gateway API sections above for configuration examples.
974+ - **Missing or incorrect `resourceUrl`**: Ensure the `resourceUrl` in your
975+ MCPServer's `oidcConfig` matches the client-facing URL (e.g.,
976+ ` https://mcp.example.com/fetch/mcp` for path-based routing).
977+ - **Token validation failure**: The token's `aud` claim must match the
978+ configured audience in your MCPServer's OIDC configuration.
979+ - **Issuer mismatch**: The token's `iss` claim must match the configured issuer.
980+ - **JWKS endpoint unreachable**: Check that the proxy can reach your identity
981+ provider's JWKS endpoint to validate tokens.
982+
983+ For detailed OAuth setup and troubleshooting, see the
984+ [Authentication and authorization](./auth-k8s.mdx) guide.
764985
765986</details>
766987
0 commit comments