Connecting to External MCP Servers¶
This guide walks through connecting MCP Gateway to an external MCP server. We use the GitHub MCP server as an example, but the pattern applies to any external service.
The guide has two parts:
- Basic setup — connect to the external server so the broker can discover and list its tools
- Per-user authentication — add OIDC and let each user supply their own upstream credential for tool calls via a custom header
Prerequisites¶
- MCP Gateway installed and configured
- Gateway API Provider (Istio) with ServiceEntry and DestinationRule support
- Network egress access to external MCP server
- Authentication credentials for the external server (if required)
- MCPGatewayExtension targeting the Gateway (required for MCPServerRegistration to work)
Note: If you're trying this locally, make local-env-setup or make local-env-setup-olm meets all prerequisites except the GitHub PAT. The optional AuthPolicy step (Step 6) additionally requires Kuadrant (make auth-example-setup).
If you haven't created an MCPGatewayExtension yet, see Configure MCP Servers for instructions.
About the GitHub MCP Server¶
The GitHub MCP server (https://api.githubcopilot.com/mcp/) provides programmatic access to GitHub functionality through the Model Context Protocol. It exposes tools for repository management, issues, pull requests, and code operations.
For this example, you'll need a GitHub Personal Access Token with read:user permissions. Get one at https://github.com/settings/tokens/new
Local Environment¶
If you have the repository checked out, you can set up a complete local environment with Keycloak, Kuadrant, and an example external MCP server:
make local-env-setup # Kind cluster with gateway
make auth-example-setup # Only needed for Auth step. Sets up Keycloak + Kuadrant + AuthPolicy prerequisites
auth-example-setup creates its own AuthPolicies (mcp-auth-policy and mcps-auth-policy in gateway-system). If you ran that command, remove the existing AuthPolicies before following this guide:
kubectl delete authpolicy mcp-auth-policy -n gateway-system --ignore-not-found
kubectl delete authpolicy mcps-auth-policy -n gateway-system --ignore-not-found
Part 1: Basic External MCP Server¶
This section registers the GitHub MCP server behind the gateway. The credentialRef provides a static credential used only by the broker to connect to the upstream server and discover available tools. User requests are handled separately — see Part 2 for per-user credentials.
Step 1: Create ServiceEntry¶
Register the external service in Istio's service registry:
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: github-mcp-external
namespace: mcp-test
spec:
hosts:
- api.githubcopilot.com
ports:
- number: 443
name: https
protocol: HTTPS
location: MESH_EXTERNAL
resolution: DNS
EOF
Step 2: Create DestinationRule¶
Configure TLS for the external service:
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: github-mcp-external
namespace: mcp-test
spec:
host: api.githubcopilot.com
trafficPolicy:
tls:
mode: SIMPLE
sni: api.githubcopilot.com
EOF
Step 3: Create HTTPRoute¶
Route traffic from your internal hostname to the external service:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: github-mcp-external
namespace: mcp-test
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: mcp-gateway
namespace: gateway-system
hostnames:
- github.mcp.local
rules:
- matches:
- path:
type: PathPrefix
value: /mcp
filters:
- type: URLRewrite
urlRewrite:
hostname: api.githubcopilot.com
backendRefs:
- name: api.githubcopilot.com
kind: Hostname
group: networking.istio.io
port: 443
EOF
The Gateway's *.mcp.local wildcard listener matches github.mcp.local. The URLRewrite filter rewrites the host header to the external service.
Step 4: Create Secret¶
Create a secret with your GitHub PAT. The broker uses this credential to connect to the upstream server and discover tools:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: github-token
namespace: mcp-test
labels:
mcp.kuadrant.io/secret: "true"
type: Opaque
stringData:
token: "Bearer $GITHUB_PAT"
EOF
Note: The
mcp.kuadrant.io/secret=truelabel is required. Without it the MCPServerRegistration will fail validation.
Step 5: Create MCPServerRegistration¶
Register the GitHub MCP server with the gateway:
kubectl apply -f - <<EOF
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: github
namespace: mcp-test
spec:
prefix: github_
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: github-mcp-external
credentialRef:
name: github-token
key: token
EOF
Step 6: Verify¶
Wait for the registration to become ready:
Expected output:
NAME PREFIX TARGET PATH READY TOOLS CREDENTIALS AGE
github github_ github-mcp-external /mcp True 41 github-token 30s
At this point the broker has discovered the GitHub tools and will list them to clients.
Step 7: Connect an MCP Client¶
Note: You may need to open Keycloak in your browser and accept the self-signed certificate if doing this locally. If you are using a Claude session, you may need to start it with
NODE_TLS_REJECT_UNAUTHORIZED=0 claudeif doing this locally.
Configure your MCP client to connect to the gateway. For Claude Code, add to .claude.json:
{
"mcpServers": {
"mcp-gateway": {
"type": "http",
"url": "http://mcp.127-0-0-1.sslip.io:8001/mcp"
}
}
}
After connecting and authenticating with Keycloak (credentials: mcp/mcp), you should see the GitHub tools listed. However, calling any tool will fail with an authentication error because no user credential is being sent to the upstream GitHub server. Part 2 addresses this.
Part 2: Per-User Authentication with x-github-pat¶
Part 1 gives the broker access for tool discovery. For users to make actual tool calls, each user supplies their own GitHub PAT via the x-github-pat header. This section adds:
- OIDC authentication on the gateway (validates user identity)
- A token replacement policy on the external server route (swaps the
x-github-patheader value into theAuthorizationheader sent upstream)
Additional Prerequisites¶
- OIDC provider configured — see Authentication (Steps 1-2)
- Kuadrant with Authorino installed — see Authentication (Step 3, Kuadrant install only)
Step 8: Create AuthPolicy for the Gateway¶
Apply an AuthPolicy to the gateway that validates OIDC tokens on all MCP requests while allowing unauthenticated access to discovery endpoints:
kubectl apply -f - <<EOF
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: mcp-auth-policy
namespace: gateway-system
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: mcp-gateway
sectionName: mcp
when:
- predicate: "!request.path.contains('/.well-known')"
rules:
authentication:
'keycloak':
jwt:
issuerUrl: https://keycloak.127-0-0-1.sslip.io:8002/realms/mcp
response:
unauthenticated:
headers:
'WWW-Authenticate':
value: Bearer resource_metadata=http://mcp.127-0-0-1.sslip.io:8001/.well-known/oauth-protected-resource/mcp
body:
value: |
{
"error": "Unauthorized",
"message": "Access denied: Authentication required."
}
unauthorized:
code: 401
headers:
'WWW-Authenticate':
value: Bearer resource_metadata=http://mcp.127-0-0-1.sslip.io:8001/.well-known/oauth-protected-resource/mcp
body:
value: |
{
"error": "Forbidden",
"message": "Access denied: Unsupported method. New authentication required (401)."
}
EOF
Replace the issuerUrl with your OIDC provider's issuer URL unless you are using the local setup environment.
Step 9: Create AuthPolicy for the External Server Route¶
Apply an AuthPolicy to the GitHub HTTPRoute that validates the x-github-pat header and replaces the Authorization header with the user's PAT:
kubectl apply -f - <<EOF
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: github-token-replacement-policy
namespace: mcp-test
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: github-mcp-external
rules:
authorization:
"has-github-pat":
patternMatching:
patterns:
- predicate: |
request.headers.exists(h, h == "x-github-pat") && request.headers["x-github-pat"] != ""
response:
unauthorized:
code: 400
body:
value: |
{
"error": "Bad Request",
"message": "Missing required x-github-pat header"
}
success:
headers:
"authorization":
plain:
expression: |
"Bearer " + request.headers["x-github-pat"]
EOF
Requests without x-github-pat get a 400 response. On success, the PAT replaces the Authorization header so the upstream server receives the user's own credential.
Step 10: Update Your MCP Client¶
Update your MCP client configuration from Step 7 to include the x-github-pat header. For Claude Code, update .claude.json:
{
"mcpServers": {
"mcp-gateway": {
"type": "http",
"url": "http://mcp.127-0-0-1.sslip.io:8001/mcp",
"headers": {
"x-github-pat": "<your-github-pat>"
}
}
}
}
Claude Code handles the OIDC login flow automatically. When the gateway returns a 401 with WWW-Authenticate, Claude Code performs OAuth discovery, redirects to the OIDC provider for authentication, and attaches the resulting access token to subsequent requests. The x-github-pat header is sent alongside the OIDC token on every request.
Step 11: Verify¶
Check that both AuthPolicies are accepted:
kubectl get authpolicy mcp-auth-policy -n gateway-system
kubectl get authpolicy github-token-replacement-policy -n mcp-test
Connect with your MCP client. You should see GitHub tools prefixed with github_. Calling the github_get_me tool via the configured mcp should return the GitHub user associated with your PAT.
Cleanup¶
# Part 1 resources
kubectl delete mcpserverregistration github -n mcp-test
kubectl delete httproute github-mcp-external -n mcp-test
kubectl delete serviceentry github-mcp-external -n mcp-test
kubectl delete destinationrule github-mcp-external -n mcp-test
kubectl delete secret github-token -n mcp-test
# Part 2 resources (if applied)
kubectl delete authpolicy mcp-auth-policy -n gateway-system --ignore-not-found
kubectl delete authpolicy github-token-replacement-policy -n mcp-test --ignore-not-found