@@ -103,7 +103,14 @@ spec:
103103 proxyPort : 8080
104104` ` `
105105
106- Create an Ingress resource to expose the MCP server proxy:
106+ Create an Ingress resource to expose the MCP server proxy. You can use either
107+ host-based routing (separate subdomain per server) or path-based routing (single
108+ domain with paths):
109+
110+ <Tabs groupId='routing-method' queryString='routing-method'>
111+ <TabItem value='host-based' label='Host-based routing' default>
112+
113+ Each MCP server gets its own subdomain:
107114
108115` ` ` yaml title="fetch-ingress.yaml"
109116apiVersion : networking.k8s.io/v1
@@ -112,28 +119,96 @@ metadata:
112119 name : fetch-mcp-ingress
113120 namespace : toolhive-system
114121 annotations :
115- # Example: for cert-manager to provision certificates
116122 cert-manager.io/cluster-issuer : ' letsencrypt-prod'
117- # Annotations vary by Ingress controller - check your controller's docs
118123spec :
119- ingressClassName : traefik # Change to match your Ingress controller
124+ ingressClassName : traefik
120125 tls :
121126 - hosts :
122- - fetch-mcp.example.com # Change to your domain
127+ - fetch-mcp.example.com
123128 secretName : fetch-mcp-tls
124129 rules :
125- - host : fetch-mcp.example.com # Change to your domain
130+ - host : fetch-mcp.example.com
126131 http :
127132 paths :
128133 - path : /
129134 pathType : Prefix
130135 backend :
131136 service :
132- name : mcp-fetch-proxy # Format: mcp-<mcpserver-name>-proxy
137+ name : mcp-fetch-proxy
138+ port :
139+ number : 8080
140+ ` ` `
141+
142+ The MCP server is accessible at ` https://fetch-mcp.example.com/mcp`.
143+
144+ </TabItem>
145+ <TabItem value='path-based' label='Path-based routing'>
146+
147+ Multiple MCP servers share a single domain using path prefixes. This approach
148+ requires URL rewriting to strip the path prefix before forwarding to the backend
149+ service.
150+
151+ :::note
152+
153+ Path rewriting syntax varies by Ingress controller. Check your controller's
154+ documentation for the correct annotations or resources.
155+
156+ :: :
157+
158+ ` ` ` yaml title="mcp-ingress.yaml"
159+ apiVersion: networking.k8s.io/v1
160+ kind: Ingress
161+ metadata:
162+ name: mcp-servers-ingress
163+ namespace: toolhive-system
164+ annotations:
165+ cert-manager.io/cluster-issuer: 'letsencrypt-prod'
166+ # Traefik example: strip path prefix
167+ traefik.ingress.kubernetes.io/router.middlewares: toolhive-system-strip-mcp-prefix@kubernetescrd
168+ spec:
169+ ingressClassName: traefik
170+ tls:
171+ - hosts:
172+ - mcp.example.com
173+ secretName: mcp-tls
174+ rules:
175+ - host: mcp.example.com
176+ http:
177+ paths:
178+ - path: /fetch
179+ pathType: Prefix
180+ backend:
181+ service:
182+ name: mcp-fetch-proxy
183+ port:
184+ number: 8080
185+ - path: /weather
186+ pathType: Prefix
187+ backend:
188+ service:
189+ name: mcp-weather-proxy
133190 port:
134- number : 8080 # This matches the proxyPort
191+ number: 8080
192+ ---
193+ # Traefik Middleware to strip path prefixes
194+ apiVersion: traefik.io/v1alpha1
195+ kind: Middleware
196+ metadata:
197+ name: strip-mcp-prefix
198+ namespace: toolhive-system
199+ spec:
200+ stripPrefix:
201+ prefixes:
202+ - /fetch
203+ - /weather
135204` ` `
136205
206+ The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
207+ ` https://mcp.example.com/weather/mcp` .
208+
209+ </TabItem>
210+ </Tabs>
211+
137212:::info[Service naming convention]
138213
139214The ToolHive operator automatically creates a Kubernetes Service for each
@@ -142,11 +217,11 @@ MCPServer named `fetch` gets a Service named `mcp-fetch-proxy`.
142217
143218:: :
144219
145- Apply both resources :
220+ Apply the resources :
146221
147222` ` ` bash
148223kubectl apply -f fetch-server.yaml
149- kubectl apply -f fetch-ingress.yaml
224+ kubectl apply -f fetch-ingress.yaml # or mcp-ingress.yaml for path-based
150225` ` `
151226
152227Verify the Ingress is configured :
@@ -155,9 +230,6 @@ Verify the Ingress is configured:
155230kubectl get ingress -n toolhive-system
156231` ` `
157232
158- The Ingress should show your configured host and address. Once DNS is
159- configured, your MCP server is accessible at `https://fetch-mcp.example.com`.
160-
161233# ## Option 2: Using Gateway API
162234
163235The [Gateway API](https://gateway-api.sigs.k8s.io/) is a more expressive way to
@@ -211,7 +283,14 @@ spec:
211283 from: Same
212284` ` `
213285
214- Create an HTTPRoute to expose your MCP server :
286+ Create an HTTPRoute to expose your MCP server. You can use either host-based
287+ routing (separate subdomain per server) or path-based routing (single domain
288+ with paths) :
289+
290+ <Tabs groupId='routing-method' queryString='routing-method'>
291+ <TabItem value='host-based' label='Host-based routing' default>
292+
293+ Each MCP server gets its own subdomain :
215294
216295` ` ` yaml title="fetch-route.yaml"
217296apiVersion: gateway.networking.k8s.io/v1
@@ -231,11 +310,71 @@ spec:
231310 port: 8080 # This matches the proxyPort
232311` ` `
233312
313+ The MCP server is accessible at `https://fetch-mcp.example.com/mcp`.
314+
315+ </TabItem>
316+ <TabItem value='path-based' label='Path-based routing'>
317+
318+ Multiple MCP servers share a single domain using path prefixes. This approach
319+ uses URL rewriting to strip the path prefix before forwarding to the backend
320+ service.
321+
322+ ` ` ` yaml title="mcp-routes.yaml"
323+ apiVersion: gateway.networking.k8s.io/v1
324+ kind: HTTPRoute
325+ metadata:
326+ name: mcp-servers-route
327+ namespace: toolhive-system
328+ spec:
329+ parentRefs:
330+ - name: mcp-gateway # Reference your Gateway name (e.g., traefik-gateway if using existing)
331+ # namespace: default # Uncomment if Gateway is in a different namespace
332+ hostnames:
333+ - mcp.example.com # Change to your domain
334+ rules:
335+ - matches:
336+ - path:
337+ type: PathPrefix
338+ value: /fetch
339+ filters:
340+ - type: URLRewrite
341+ urlRewrite:
342+ path:
343+ type: ReplacePrefixMatch
344+ replacePrefixMatch: /
345+ backendRefs:
346+ - name: mcp-fetch-proxy # Format: mcp-<mcpserver-name>-proxy
347+ port: 8080 # This matches the proxyPort
348+ - matches:
349+ - path:
350+ type: PathPrefix
351+ value: /weather
352+ filters:
353+ - type: URLRewrite
354+ urlRewrite:
355+ path:
356+ type: ReplacePrefixMatch
357+ replacePrefixMatch: /
358+ backendRefs:
359+ - name: mcp-weather-proxy
360+ port: 8080
361+ ` ` `
362+
363+ The MCP servers are accessible at `https://mcp.example.com/fetch/mcp` and
364+ ` https://mcp.example.com/weather/mcp` .
365+
366+ The `URLRewrite` filter removes the path prefix (e.g., `/fetch`) before
367+ forwarding requests to the backend service, so the MCP server receives requests
368+ at `/mcp` as expected.
369+
370+ </TabItem>
371+ </Tabs>
372+
234373Apply the resources :
235374
236375` ` ` bash
237- kubectl apply -f mcp-gateway.yaml
238- kubectl apply -f fetch-route.yaml
376+ kubectl apply -f mcp-gateway.yaml # If creating a new Gateway
377+ kubectl apply -f fetch-route.yaml # or mcp-routes.yaml for path-based
239378` ` `
240379
241380Verify the route is configured :
@@ -249,7 +388,7 @@ kubectl get httproute -n toolhive-system
249388For production deployments, use valid TLS certificates from a trusted
250389certificate authority. The most common approaches are :
251390
252- <Tabs groupId='cert-method' queryString='cert-method' >
391+ <Tabs groupId='cert-method'>
253392<TabItem value='cert-manager' label='cert-manager' default>
254393
255394[cert-manager](https://cert-manager.io/) automates certificate management in
@@ -314,7 +453,9 @@ In the ToolHive UI:
3144532. Select **Add a remote MCP server**
3154543. Enter the connection details :
316455 - **Name**: A friendly name for the server
317- - **Server URL**: `https://fetch-mcp.example.com/mcp`
456+ - **Server URL**: Use the appropriate URL based on your routing approach:
457+ - Host-based : ` https://fetch-mcp.example.com/mcp`
458+ - Path-based : ` https://mcp.example.com/fetch/mcp`
318459 - **Transport**: Streamable HTTP (or SSE if your server uses SSE)
3194604. If authentication is configured, select the method and enter the required
320461 OAuth or OIDC details
@@ -329,8 +470,11 @@ MCP client.
329470Use the `thv run` command to connect :
330471
331472` ` ` bash
332- # Basic connection without authentication
473+ # Host-based routing: separate subdomain per server
333474thv run --name fetch-k8s https://fetch-mcp.example.com/mcp
475+
476+ # Path-based routing: single domain with paths
477+ thv run --name fetch-k8s https://mcp.example.com/fetch/mcp
334478` ` `
335479
336480If authentication is configured, add the appropriate flags. See the
@@ -454,9 +598,15 @@ thv mcp list tools --server https://fetch-mcp.example.com/mcp
454598Or use `curl` to send a JSON-RPC request :
455599
456600` ` ` bash
601+ # Host-based routing
457602curl -X POST https://fetch-mcp.example.com/mcp \
458603 -H "Content-Type: application/json" \
459604 -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
605+
606+ # Path-based routing
607+ curl -X POST https://mcp.example.com/fetch/mcp \
608+ -H "Content-Type: application/json" \
609+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
460610` ` `
461611
462612You should receive a JSON response with a list of available tools.
0 commit comments