Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions config/samples/policy-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# MCP Gateway policy demo

This sample deploys a small local catalog of MCP servers that can be used to test federation, virtual servers, backend credentials, custom paths, and tool-level authorization together.

It uses the existing test servers in `config/test-servers` and registers five of them with stable prefixes:

| Registration | Prefix | Purpose |
| --- | --- | --- |
| `test-server1` | `test1_` | Go SDK tools such as `greet`, `time`, and `headers` |
| `test-server2` | `test2_` | Go SDK tools such as `hello_world`, `time`, and `headers` |
| `test-server3` | `test3_` | Python FastMCP tools such as `add`, `dozen`, `pi`, and `get_weather` |
| `api-key-server` | `apikey_` | backend credential forwarding through `credentialRef` |
| `custom-path-server` | `custompath_` | MCP served from `/v1/special/mcp` |

## Apply the demo

Start from a local environment created by `make local-env-setup`, then apply the sample:

```bash
kubectl apply -k config/samples/policy-demo
```

Wait for the registrations to become ready:

```bash
kubectl get mcpserverregistration -n mcp-test
kubectl get mcpvirtualserver -n mcp-test
```

The gateway status endpoint should show the registered servers after discovery:

```bash
kubectl port-forward -n mcp-system deploy/mcp-gateway 8080:8080
curl -s http://localhost:8080/status | jq '.servers[] | {name, ready, totalTools}'
```

## Virtual server views

The sample creates three virtual servers:

| Virtual server | Expected tools |
| --- | --- |
| `mcp-test/dev-tools` | `test1_greet`, `test1_headers`, `test2_hello_world`, `test2_headers` |
| `mcp-test/data-tools` | `test2_time`, `test3_time`, `test3_add`, `test3_dozen`, `test3_pi`, `test3_get_weather` |
| `mcp-test/operations-tools` | `apikey_hello_world`, `custompath_echo_custom`, `custompath_path_info`, `custompath_timestamp` |

Use the `X-Mcp-Virtualserver` header when calling `tools/list` to see one focused view:

```bash
curl -s -D /tmp/mcp_headers -X POST http://mcp.127-0-0-1.sslip.io:8001/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"policy-demo","version":"1.0.0"}}}'

SESSION_ID=$(grep -i "mcp-session-id:" /tmp/mcp_headers | cut -d' ' -f2 | tr -d '\r')

curl -s -X POST http://mcp.127-0-0-1.sslip.io:8001/mcp \
-H "Content-Type: application/json" \
-H "mcp-session-id: $SESSION_ID" \
-H "X-Mcp-Virtualserver: mcp-test/dev-tools" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | jq '.result.tools[].name'
```

## Authorization checks

Apply the optional `AuthPolicy` after the authentication guide is working:

```bash
kubectl apply -f config/samples/policy-demo/authpolicy.yaml
```

The policy uses the same role claim shape as the authorization guide. It checks each `tools/call` request against:

- `x-mcp-servername`, set by the router to the selected `MCPServerRegistration`
- `x-mcp-toolname`, set by the router to the unprefixed upstream tool name
- `auth.identity.resource_access`, read from the JWT

With the local Keycloak setup from the authentication guide, the `mcp` user can call allowed tools such as `test1_greet` and `test2_headers`. A call to a tool that is not in the user's roles, such as `test1_time`, should return `403 Forbidden`.

## Remove the demo

```bash
kubectl delete -k config/samples/policy-demo
kubectl delete -f config/samples/policy-demo/authpolicy.yaml --ignore-not-found
```
40 changes: 40 additions & 0 deletions config/samples/policy-demo/authpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: policy-demo-tool-auth
namespace: gateway-system
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: mcp-gateway
sectionName: mcps
rules:
authentication:
'keycloak':
jwt:
issuerUrl: https://keycloak.127-0-0-1.sslip.io:8002/realms/mcp
authorization:
'tool-access-check':
patternMatching:
patterns:
- predicate: |
request.headers['x-mcp-toolname'] in (has(auth.identity.resource_access) && auth.identity.resource_access.exists(p, p == request.headers['x-mcp-servername']) ? auth.identity.resource_access[request.headers['x-mcp-servername']].roles : [])
response:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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": "MCP Tool Access denied: Authentication required."
}
unauthorized:
body:
value: |
{
"error": "Forbidden",
"message": "MCP Tool Access denied: Insufficient permissions for this tool."
}
7 changes: 7 additions & 0 deletions config/samples/policy-demo/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../test-servers
- mcpserverregistrations.yaml
- mcpvirtualservers.yaml
73 changes: 73 additions & 0 deletions config/samples/policy-demo/mcpserverregistrations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: test-server1
namespace: mcp-test
labels:
mcp.kuadrant.io/managed: 'true'
spec:
toolPrefix: test1_
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: mcp-server1-route
---
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: test-server2
namespace: mcp-test
labels:
mcp.kuadrant.io/managed: 'true'
spec:
toolPrefix: test2_
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: mcp-server2-route
---
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: test-server3
namespace: mcp-test
labels:
mcp.kuadrant.io/managed: 'true'
spec:
toolPrefix: test3_
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: mcp-server3-route
---
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: api-key-server
namespace: mcp-test
labels:
mcp.kuadrant.io/managed: 'true'
spec:
toolPrefix: apikey_
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: mcp-api-key-server-route
credentialRef:
name: api-key-server-credentials
key: token
---
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPServerRegistration
metadata:
name: custom-path-server
namespace: mcp-test
labels:
mcp.kuadrant.io/managed: 'true'
spec:
path: /v1/special/mcp
toolPrefix: custompath_
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: mcp-custom-path-server
40 changes: 40 additions & 0 deletions config/samples/policy-demo/mcpvirtualservers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPVirtualServer
metadata:
name: dev-tools
namespace: mcp-test
spec:
description: "Developer-focused tools from the Go SDK test servers"
tools:
- test1_greet
- test1_headers
- test2_hello_world
- test2_headers
---
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPVirtualServer
metadata:
name: data-tools
namespace: mcp-test
spec:
description: "Data and utility tools from the Python FastMCP server"
tools:
- test2_time
- test3_time
- test3_add
- test3_dozen
- test3_pi
- test3_get_weather
---
apiVersion: mcp.kuadrant.io/v1alpha1
kind: MCPVirtualServer
metadata:
name: operations-tools
namespace: mcp-test
spec:
description: "Backend-authenticated and custom path tools"
tools:
- apikey_hello_world
- custompath_echo_custom
- custompath_path_info
- custompath_timestamp