Skip to content

Commit c2fdd9d

Browse files
committed
Add path-based routing
Signed-off-by: Dan Barr <[email protected]>
1 parent 9405c5b commit c2fdd9d

File tree

1 file changed

+169
-19
lines changed

1 file changed

+169
-19
lines changed

docs/toolhive/guides-k8s/connect-clients.mdx

Lines changed: 169 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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"
109116
apiVersion: 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
118123
spec:
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

139214
The 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
148223
kubectl 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

152227
Verify the Ingress is configured:
@@ -155,9 +230,6 @@ Verify the Ingress is configured:
155230
kubectl 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

163235
The [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"
217296
apiVersion: 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+
234373
Apply 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

241380
Verify the route is configured:
@@ -249,7 +388,7 @@ kubectl get httproute -n toolhive-system
249388
For production deployments, use valid TLS certificates from a trusted
250389
certificate 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:
314453
2. Select **Add a remote MCP server**
315454
3. 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)
319460
4. If authentication is configured, select the method and enter the required
320461
OAuth or OIDC details
@@ -329,8 +470,11 @@ MCP client.
329470
Use the `thv run` command to connect:
330471

331472
```bash
332-
# Basic connection without authentication
473+
# Host-based routing: separate subdomain per server
333474
thv 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

336480
If authentication is configured, add the appropriate flags. See the
@@ -454,9 +598,15 @@ thv mcp list tools --server https://fetch-mcp.example.com/mcp
454598
Or use `curl` to send a JSON-RPC request:
455599

456600
```bash
601+
# Host-based routing
457602
curl -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

462612
You should receive a JSON response with a list of available tools.

0 commit comments

Comments
 (0)