mcp-opa-authz
Provides tools for querying authorization decisions from an OpenID AuthZEN-compliant Policy Decision Point (PDP). Supports single access evaluations (subject/action/resource) over POST /access/v1/evaluation, batched evaluations with execute_all / deny_on_first_deny / permit_on_first_permit semantics, and PDP metadata discovery via /.well-known/authzen-configuration. Correlates decisions with PDP logs through X-Request-ID, validating AuthZEN's subject/action/resource information model before sending, and treating missing decisions or authentication failures as errors rather than denials.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-opa-authzIs Alice allowed to delete doc-42?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-opa-authz
Stop letting your agent guess at authorization. This is an MCP server that answers "is this allowed?" from real policy code — either Rego you hand it, or the OpenID AuthZEN 1.0 PDP that actually governs your system.
Ask a model whether Alice may delete that document and it will produce a confident, plausible, unfalsifiable answer. Give it these tools and the answer comes from the policy.
brew install kanywst/tap/mcp-opa-authz
# or, without Homebrew:
go install github.com/kanywst/mcp-opa-authz@latest
claude mcp add opa-authz -- mcp-opa-authzThat is enough for evaluate_policy. Point it at a PDP to get the rest:
claude mcp add opa-authz \
--env AUTHZEN_PDP_URL=http://localhost:8181/access/v1/evaluation \
-- mcp-opa-authzTwo layers, same question
Tool | Answers | Needs |
| "What does this Rego say?" Evaluated in-process by OPA. | Nothing external |
| "What does the PDP that governs this system say?" | A reachable PDP |
| The same, over a list — which of these may the subject touch? | A reachable PDP |
| "Which endpoints does this PDP offer?" | A reachable PDP |
Use evaluate_policy while authoring or debugging a policy you have the source of. Use authzen_evaluate when the decision has to come from production, not from a policy pasted into the chat. The server's MCP instructions tell the model the same thing, so it usually picks correctly on its own.
Related MCP server: enterprise-auth-mcp-server
What makes this different from opa eval in a shell tool
An agent given shell access can already run opa eval. What it cannot do is get an answer it is allowed to trust:
Undefined is not false. A Rego query with no matching rule and no default returns
[]. Every model reads that as a deny.evaluate_policyreturnsdefinedalongsidevalue, so "the policy denied" and "the policy has no opinion" stop being the same answer.A PDP that did not answer is not a deny. AuthZEN makes
decisiona required member. A response missing it decodes into a Goboolasfalse— a broken PDP would look like a strict one. This server treats a missingdecisionas a failure, never as a deny. Same for a 401: that means this server failed to authenticate, not that the subject was denied, and the error says so.The policy runs in a sandbox. Rego handed to an MCP server was written by a model, from text that may have come from a web page. OPA's
http.sendwould let that policy make arbitrary HTTP requests from your laptop, andopa.runtime()would hand it your environment. Both are compiled out. See Security.Batch decisions carry their index.
permit_on_first_permitlegitimately returns fewer decisions than you sent. Zipping the arrays would attach a decision to the wrong resource.
Demo
Debugging a policy that is denying when it should not:
> Why is bob getting denied on doc-1? Here's the policy and the input.
evaluate_policy(rego=…, query="data.rbac.allow", input_json=…, trace=true)
{
"defined": true,
"value": false,
"printed": ["checking roles for", "bob"],
"trace": [
"Enter data.rbac.allow",
"| Eval some role in roles[input.user]",
"| Fail roles[\"bob\"]",
…
]
}
Bob has no entry in `roles` at all — the rule never reaches the permission
check. Adding "bob": {"viewer"} fixes it.Then confirming against the PDP that actually runs:
> Does production agree?
authzen_evaluate(subject={"type":"user","id":"bob"}, …)
{ "decision": false, "context": { "reason": "no role binding" },
"request_id": "3f9c…", "pdp_url": "https://pdp.internal/access/v1/evaluation" }Tools
evaluate_policy
Param | Required | Description |
| yes | Rego source with a |
| yes | Rego query, e.g. |
| no | JSON-encoded |
| no | JSON-encoded base document for the |
| no |
|
| no | Return a pretty-printed evaluation trace. Verbose; bounded at 4000 events, 200 lines, 1 KiB per line. |
Returns defined, value, the raw OPA result_set (omitted with result_set_omitted past 256 KiB encoded), any print() output (200 lines of 1 KiB), and the trace when asked for.
authzen_evaluate
Param | Required | Description |
| yes | JSON object. AuthZEN requires |
| yes | JSON object. AuthZEN requires |
| yes | JSON object. AuthZEN requires |
| no | JSON object with runtime context (IP, time, MFA strength). |
| no | Override |
Returns decision, the PDP's context if any, the pdp_url that answered, and the request_id sent as X-Request-ID — so a decision in a transcript can be found in the PDP's logs.
authzen_evaluate_batch
Same arguments, plus evaluations (a JSON array whose entries override the top-level defaults) and evaluations_semantic (execute_all, deny_on_first_deny, permit_on_first_permit). Capped at 100 entries per call.
{
"subject": "{\"type\":\"user\",\"id\":\"alice\"}",
"action": "{\"name\":\"read\"}",
"evaluations": "[{\"resource\":{\"type\":\"doc\",\"id\":\"1\"}},{\"resource\":{\"type\":\"doc\",\"id\":\"2\"}}]"
}authzen_discover
Fetches /.well-known/authzen-configuration from a PDP root. pdp_url may be a root or an evaluation endpoint — the known AuthZEN path suffix is stripped, and a PDP mounted under a prefix keeps its prefix.
Standards conformance
Implements Authorization API 1.0, approved as an OpenID Final Specification in January 2026:
Section | Status |
Access Evaluation ( |
|
Access Evaluations, batch ( |
|
PDP Metadata ( |
|
Subject / Action / Resource information model | Required members validated before the request is sent |
| Sent on every call, returned in the result |
Search APIs (subject / resource / action) | Not implemented — open an issue if you need them |
Related work worth knowing about: the AuthZEN working group's COAZ profile binds AuthZEN to MCP tool calls themselves, so a gateway can authorize tools/call with an x-authzen-mapping declared in a tool's inputSchema. That is the enforcement side of the same problem — this server is the inspection side, and the two compose.
Configuration
Variable | Default | Description |
| — | Default Access Evaluation endpoint. |
| — |
|
|
| Per-request timeout. |
|
| Response read limit. |
|
| Wall-clock limit on one Rego evaluation. |
|
| Per-argument size limit. |
|
| Re-enable the network built-ins. See below. |
A malformed value stops the server at startup rather than being silently replaced by the default — a bound an operator believes is in place should be in place.
Security
evaluate_policy compiles and runs Rego that a model produced, inside this process. Three OPA built-ins are removed from the capability set for that reason:
Built-in | Why |
| Arbitrary HTTP from wherever the server runs — a laptop or CI runner, inside whatever network boundary that sits behind. It is the whole SSRF surface, and it contradicts the tool's own description. |
| Enough to exfiltrate |
| Returns the runtime configuration, including the process environment and every credential in it. |
Time, JWT, UUID and random built-ins are untouched — those appear in real authorization policies. A policy using a removed built-in fails to compile with a message naming it, and pointing at MCP_OPA_ALLOW_NETWORK_BUILTINS for the cases where you genuinely want it.
The PDP client is constrained too: pdp_url must be an absolute http(s) URL with a host and no userinfo, redirects are refused rather than followed with the Authorization header attached, responses are read through a byte cap, and PDP error bodies are truncated before they reach the model's context.
Reporting a vulnerability: see SECURITY.md.
Running it
From source
make smokeBuilds the binary, stands up a fake AuthZEN PDP, drives one real MCP stdio session through all four tools, and asserts each answered — including that http.send is still rejected. No MCP client and no real PDP needed.
Container
docker run -i --rm \
-e AUTHZEN_PDP_URL=https://pdp.example.com/access/v1/evaluation \
ghcr.io/kanywst/mcp-opa-authzCursor and other MCP clients
{
"mcpServers": {
"opa-authz": {
"command": "mcp-opa-authz",
"env": { "AUTHZEN_PDP_URL": "http://localhost:8181/access/v1/evaluation" }
}
}
}A local opa-authzen-plugin on :8181 works as the PDP. So does Topaz, Cerbos, Keycloak with its experimental AuthZEN support, or anything else on the interop list.
Examples
examples/ has reference Rego policies for evaluate_policy:
rbac.rego— role to permission mappingabac.rego— clearance level comparisonk8s_admission.rego— admission control: required labels
Layout
Flat on purpose. A single-binary MCP server does not need cmd/, internal/ or pkg/.
main.go server bootstrap, CLI, tool registration
config.go environment, bounds, defaults
args.go tool argument decoding
tool_opa.go evaluate_policy
opa_capabilities.go the Rego sandbox
authzen.go AuthZEN 1.0 wire types and PDP client
tool_authzen.go authzen_evaluate, _batch, _discover
scripts/smoke.sh end-to-end MCP sessionVerifying a release
Releases ship a cosign-signed checksum file (Sigstore keyless via GitHub OIDC) and a CycloneDX SBOM per archive. The signature and its certificate travel together in one Sigstore bundle, *-checksums.txt.sigstore.json.
TAG=v0.3.0
gh release download "$TAG" -R kanywst/mcp-opa-authz -p '*-checksums.txt*'
cosign verify-blob \
--bundle "mcp-opa-authz-${TAG#v}-checksums.txt.sigstore.json" \
--certificate-identity-regexp 'https://github.com/kanywst/mcp-opa-authz/.github/workflows/release.yml@refs/tags/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
"mcp-opa-authz-${TAG#v}-checksums.txt"Then check the archive you downloaded against that file:
sha256sum -c "mcp-opa-authz-${TAG#v}-checksums.txt" --ignore-missingContributing
Issues and pull requests are welcome — see CONTRIBUTING.md. Good first contributions: another example policy, a PDP this has not been tried against, or a gap against the AuthZEN text.
This repo is the merge of the former 0-draft/mcp-opa and 0-draft/mcp-authzen. Both histories are preserved here.
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
- StytchOAuthdev.stytch.mcp
The Stytch MCP server is a reference implementation that demonstrates remote MCP server authentication and authorization using Stytch Connected Apps. It provides OAuth 2.1-compliant authorization (including PKCE), Dynamic Client Registration, and validates Stytch-issued access tokens to enable AI agents to securely interact with external services through permissioned access, supporting scopes like openid, email, profile, and manage:project_data.
MCP server for building and testing AI agents with multi-model experimentation and insights.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP Server for AI agent identity and authorization. Create, verify, and manage agent identities with trust scores and scoped authorization tokens.MIT
- AlicenseAqualityDmaintenanceMCP server for enterprise authentication and authorization — JWT validation, OIDC token inspection, OAuth 2.0 introspection, and role-based access control for AI agents.8MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to discover and execute tools via a secure MCP server with JWT authentication, RBAC, rate limiting, and audit logging.1MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that enforces runtime governance on AI agent actions — file access, command execution, delegation chains, and permission escalation.MIT