A Kubernetes controller that translates AccessPolicy custom resources into Kuadrant AuthPolicy objects, enabling declarative, tool-level access control for MCP (Model Context Protocol) servers running behind kuadrant/mcp-gateway.
The AccessPolicy controller bridges the gap between high-level, gateway-agnostic MCP authorization intent and the concrete enforcement mechanisms provided by Kuadrant's Authorino. It watches AccessPolicy resources that target Gateway objects and performs two key tasks:
- CEL Translation — Converts domain-specific variables like
request.mcp.tool_nameinto the data-plane equivalents (request.headers['x-mcp-toolname']) that Authorino can evaluate at runtime. - Policy Aggregation — Combines multiple
AccessPolicyrules targeting the same Gateway into a single KuadrantAuthPolicy, satisfying Kuadrant's 1:1 policy-to-target constraint.
┌──────────────┐ ┌────────────────────────┐ ┌────────────────┐
│ AccessPolicy│────▶│ AccessPolicy Controller│────▶│ AuthPolicy │
│ (user-facing)│ │ • CEL translation │ │ (Kuadrant CRD) │
└──────────────┘ │ • Policy aggregation │ └───────┬────────┘
└────────────────────────┘ │
▼
┌──────────────┐
│ Authorino │
│ (enforcement)│
└──────────────┘
apiVersion: agentic.networking.x-k8s.io/v1alpha1
kind: AccessPolicy
metadata:
name: web-search-policy
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: prod-mcp-gateway
rules:
- name: allow-search-web-only
authorization:
type: CEL
cel:
expression: "request.mcp.tool_name == 'search_web'"The controller translates request.mcp.tool_name → request.headers['x-mcp-toolname'] and produces an AuthPolicy with pattern-matching predicates that Authorino evaluates at the data plane.
The controller reports progress through standard Kubernetes conditions on each AccessPolicy:
| Condition | Meaning |
|---|---|
Accepted |
The policy's CEL rules compiled successfully |
ResolvedRefs |
The target Gateway was found in the cluster |
Programmed |
The resulting AuthPolicy was successfully applied |
The fastest way to see the controller in action is the one-command quickstart. It spins up a local Kind cluster with everything pre-configured — including Kuadrant, the MCP Gateway, and a sample MCP server. We then use the official MCP Inspector to interact with the tools and see access policies enforced in real time.
make quickstartThis will:
- Create a Kind cluster (
accesspolicy-demo) - Install Gateway API CRDs, the Kuadrant operator, and MCP Gateway
- Build & deploy the accesspolicy-controller
- Deploy an MCP server with sample tools (
get-sum,echo,get-tiny-image, etc.) - Apply an
AccessPolicythat allows onlyget-sumandecho - Port-forward the Envoy Gateway to
localhost:8080
Open a new terminal and run the official MCP Inspector to connect to the Gateway:
npx -y @modelcontextprotocol/inspector http://localhost:8080/sseIn the Inspector UI, try calling the tools:
| Tool Used | Expected Result |
|---|---|
get-sum |
✅ Allowed |
echo |
✅ Allowed |
get-tiny-image |
❌ Blocked |
Swap echo → get-tiny-image in the allow list with a single command:
kubectl apply -f quickstart/policy/updated-policy.yamlNow get-tiny-image is ✅ allowed and echo is ❌ blocked — no restarts needed.
make quickstart-cleanThe AccessPolicy controller allows multiple AccessPolicy custom resources to target the same Gateway. It aggregates all these policies into a single Kuadrant AuthPolicy.
To see this in action:
make demo-multiThis demo deploys the same MCP infrastructure as the quickstart, but applies two independent AccessPolicy resources created by different teams:
- Team A's policy allows
get-sum. - Team B's policy allows
echo.
In the MCP Inspector UI, verify that both tools are ✅ Allowed, while other tools remain ❌ Blocked.
Cleanup:
make demo-multi-cleanThe AccessPolicy controller is distributed as a Kubernetes CRD and controller.
- Access to a Kubernetes v1.11.3+ cluster
- Gateway API CRDs installed
- Kuadrant Operator deployed (provides
AuthPolicyCRD and Authorino)
You can install the controller directly from the generated manifest in the main branch (or a specific release tag):
kubectl apply -f https://raw.githubusercontent.com/kuadrant/accesspolicy-controller/main/dist/install.yamlIf you prefer using Helm, a chart is available in the dist/chart directory:
git clone https://github.com/kuadrant/accesspolicy-controller.git
cd accesspolicy-controller
helm install accesspolicy-controller ./dist/chart -n accesspolicy-system --create-namespaceIf you want to contribute, build the project from source, or run it locally, follow these steps.
- Go v1.24.6+
- Docker v17.03+
- kubectl v1.11.3+
- Access to a Kubernetes cluster with Gateway API and Kuadrant installed
1. Build and push your image to a registry you can access:
export IMG=<some-registry>/accesspolicy:tag
make docker-build docker-push IMG=$IMG2. Deploy the CRDs and Controller to the cluster:
make deploy IMG=$IMGNOTE: If you encounter RBAC errors, you may need to grant yourself cluster-admin privileges.
Create instances of your solution:
kubectl apply -k config/samples/Delete the instances and controller:
kubectl delete -k config/samples/
make undeploy
make uninstallFor development, you can run the controller against your current kubeconfig context:
# Install CRDs
make install
# Run the controller locally
make runThen apply an AccessPolicy in another terminal:
kubectl apply -f config/samples/agentic_v1alpha1_accesspolicy.yamlRun all unit and integration tests (uses envtest for a real K8s API + etcd):
make testRun only the translator unit tests:
go test ./internal/translator/...Run the linter:
make lintRun the conformance tests (spins up a local Kind cluster, deploys the controller, and runs the official kube-agentic-networking conformance suite):
make test-conformanceTo generate the dist/install.yaml single-file installer:
make build-installer IMG=ghcr.io/kuadrant/accesspolicy-controller:latestTo update the Helm chart when changing manifests:
kubebuilder edit --plugins=helm/v2-alpha --force├── api/v1alpha1/ # AccessPolicy CRD types and deepcopy
├── cmd/main.go # Manager entrypoint
├── config/
│ ├── crd/bases/ # Generated CRD manifests (do not edit)
│ ├── rbac/ # Generated RBAC (do not edit)
│ └── samples/ # Example AccessPolicy CRs
├── internal/
│ ├── controller/ # AccessPolicy reconciler
│ └── translator/ # CEL macro translation and validation
├── quickstart/ # One-command demo environment
│ ├── run-quickstart.sh # Orchestration script (make quickstart)
│ ├── kind-config.yaml # Kind cluster config
│ ├── agent/ # ADK-based AI agent with web UI
│ ├── mcpserver/ # MCP "everything" server
│ └── policy/ # Sample Gateway + AccessPolicy resources
└── docs/ # Project documentation
├── user_guide.md # How to use AccessPolicy and write CEL rules
├── design.md # Architecture and design decisions
├── tasks.md # Implementation task breakdown
├── implementation_guide.md # Step-by-step implementation guide
└── demo.md # End-to-end demo walkthrough