Part 4 ended where the MCP spec ends: the token is valid for this server. This part covers the question that comes next on every request. May this agent, for this user, call this tool with these arguments, right now? The standard for asking it is the OpenID AuthZEN Authorization API 1.0, Final since January 2026. The standard for mapping MCP onto it is the COAZ-MCP binding, Draft 1.
Why tool calls need their own decision
A token answers questions fixed at issuance: who, which client, which audience, which scopes. A tool call raises questions that only exist at call time:
- Arguments.
transfer_fundswith 25 EUR and with 25,000 EUR carry the same scope. - State. What the agent has already done in this session, the user’s current risk, whether the payee was added minutes ago.
- Who is really asking. The same user may be allowed something directly that an agent acting for them should not do.
Putting these checks inside every MCP server spreads policy across code nobody reviews together. Externalizing them gives one place to write, test and audit the rules: a Policy Decision Point (PDP) that a Policy Enforcement Point (PEP) asks before letting each call through.
The AuthZEN Authorization API in detail
AuthZEN defines how a PEP asks and a PDP answers, over HTTPS POST with JSON. It does not define a policy language; any engine can sit behind it.
Single evaluation. POST /access/v1/evaluation with four parts. subject needs type and id, resource needs type and id, action needs name, and each can carry properties. context is optional. The answer is a boolean decision with an optional context object for the PEP. A deny is a normal 200 response with "decision": false, not an HTTP error. HTTP errors (400, 401, 403, 500) mean the call itself failed.
Batch evaluations. POST /access/v1/evaluations carries an evaluations array. Top-level subject, action, resource and context act as defaults that each entry can override. options.evaluations_semantic controls how far the PDP goes:
| Semantic | Behaviour | Agent use |
|---|---|---|
execute_all (default) | Evaluate everything, return every result | Check which of a list of tools or records the agent may use |
deny_on_first_deny | Stop at the first deny or failure | A multi-step action that needs every step allowed |
permit_on_first_permit | Stop at the first permit | Any one of several grants is enough |
Search. Three endpoints answer “what is allowed?” rather than “is this allowed?”: /access/v1/search/subject returns permitted subjects, /access/v1/search/resource returns permitted resources of a type, and /access/v1/search/action returns permitted actions on a resource. Results can be paged with an opaque next_token.
Metadata. A PDP publishes its endpoints at /.well-known/authzen-configuration. policy_decision_point and access_evaluation_endpoint are required, and a missing endpoint means the PDP does not support that API.
Correlation. A PEP may send X-Request-ID, and the PDP should echo it. Part 7 uses this to tie decision logs to requests.
Mapping MCP to AuthZEN: COAZ-MCP
AuthZEN speaks subject, action, resource and context; MCP speaks JSON-RPC methods and tool arguments. COAZ-MCP (Draft 1, February 2026, by authors at CrowdStrike and Cerbos) is the binding between them, built on the protocol-neutral COAZ Framework.
Default mapping
With no configuration, the PEP maps a tools/call like this: the subject is type identity with the token’s sub as its id, the action is named tools/call, the resource is type tool with the tool’s name as its id, and the agent goes into context from the token’s client_id. Defaults also exist for tools/list, the resources/* and prompts/* methods, completion/complete, logging/setLevel and the tasks/* methods. ping and notifications pass through without a PDP call, and unknown methods are denied.
Declared mappings
A tool can declare its own mapping, so the PDP sees the business action and the real resource instead of “tool X was called”. The mapping lives in the tool’s inputSchema as x-authzen-mapping, published in the tools/list response so gateways and clients can read it. Values starting with $ are CEL expressions over two inputs: params, the request parameters including tool arguments, and token, the decoded access token claims. The structure, with illustrative values:
{
"name": "transfer_funds",
"inputSchema": {
"type": "object",
"properties": {
"account": { "type": "string" },
"amount": { "type": "number" },
"payee": { "type": "string" }
},
"required": ["account", "amount", "payee"],
"x-authzen-mapping": {
"evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"action": { "name": "transfer_funds" },
"resource": { "type": "account", "id": "$params.arguments.account" },
"context": {
"agent": "$token.?client_id",
"amount": "$params.arguments.amount",
"payee": "$params.arguments.payee"
}
}
}
}
}
The .? operator makes an optional claim disappear when absent instead of raising an error. A mapping uses either evaluation for one decision or evaluations for several, and nothing else. If your tokens carry the human in a different claim from sub, the PEP and the token issuer can agree to read the subject from that claim, with the agent kept in context.agent.
What happens on a deny
The PEP refuses the message. COAZ-MCP returns a JSON-RPC error with code -32001 and the request’s id, or a tool result with isError: true. A mapping failure returns -32602 and a PDP communication failure -32603. In every case the message does not take effect: COAZ-MCP fails closed.
Rules worth copying from its security section
- Validate the token first. Signature, issuer, audience and expiry, before any claim feeds the mapping.
- Trust only the verified subject. If a mapping sets
subject.idfrom the token, the PEP must check it matches the validated token. Everything else a server declares, including context, action and resource values, is untrusted input, and policies should not grant privilege on it. - What isn’t mapped isn’t checked. The PDP only sees what the mapping projects. If a rule depends on an argument, the mapping must include it.
- Some messages are out of scope. Server-initiated requests, such as
sampling/createMessageandelicitation/create, are not covered in this version.
Where the PEP lives
COAZ-MCP allows two places to enforce, and the choice shapes your operations more than your security.
A gateway (option A) gives one enforcement point for many servers, including third-party servers you cannot change, as long as they publish mappings in tools/list. Enforcing in the server (option B) needs no extra component, but every server team has to get validation and mapping right. Either way, one component should own the decision for a given message: COAZ-MCP allows both to call the PDP, but calls it redundant.
Writing policies for agents
AuthZEN deliberately leaves the policy language to the PDP, and so does the IETF’s draft on agents (draft-ietf-wimse-aims). What the standards fix is the inputs a policy can see. That makes the useful exercise this: for each rule, which inputs must the mapping project?
| Rule, in plain words | Inputs the PDP needs | Where they come from |
|---|---|---|
| Agents may read customer records only for customers assigned to the user | Subject, resource id, the user’s assignments | token.sub, params.arguments, PDP’s own data |
| Agents may move up to 1,000 EUR to known payees; anything else needs the user | Amount, payee, whether the payee is known | params.arguments, PDP’s own data |
| No agent may read HR compensation data, whatever the user may read | Agent identity, resource type | token.client_id, resource mapping |
| Only the finance assistant may call payment tools | Agent identity, action | token.client_id, action mapping |
| Deny every call from a user flagged by a risk signal | Subject, current risk state | token.sub, signals the PDP receives (Part 7) |
Two patterns come out of this table. First, agent identity is just another input: policy keys on context.agent, which comes from the token’s client_id, so Part 2’s work on giving each agent its own client ID pays off here. Second, the arguments that matter must be projected by the mapping, or the PDP never sees them. COAZ-MCP’s security section says exactly that.
The second row is also where per-call authorization meets the human. A deny can carry context telling the PEP the call is approvable rather than forbidden. The AuthZEN Access Request and Approval Profile, a working-group draft, standardizes that path, and Part 6 covers it.
Performance and failure handling
Every PDP call sits in the request path, so how it fails matters as much as how fast it answers.
- Fail closed by default. COAZ-MCP already does: a mapping error or an unreachable PDP refuses the message. If you relax this for a read-only, low-risk tool, make it a written decision per tool, not a gateway default.
- Batch where calls fan out. One
evaluationsrequest withdeny_on_first_denychecks a multi-step plan in a single round trip, andexecute_allchecks a list of candidates at once. - Cache with care. Caching decisions saves latency, but draft-ietf-wimse-aims is explicit: after a revocation or risk notice, cached authorization decisions and tokens that are no longer valid must not continue to be used. A cache needs an invalidation path driven by the signals in Part 7.
- Don’t call the PDP twice. COAZ-MCP allows both a gateway and the server to enforce, but calls it redundant. Pick one owner per message.
- Correlate everything. Send
X-Request-IDon each evaluation so a deny can be traced from the agent’s error back to the rule that produced it.
Standards status (November 2026)
| Document | Status |
|---|---|
| AuthZEN Authorization API 1.0 | OpenID Final Specification (January 2026) |
| COAZ Framework 1.0 | OpenID AuthZEN working-group draft |
| COAZ-MCP Binding 1.0 | Working-group draft, Draft 1 (13 February 2026) |
| AuthZEN Access Request and Approval Profile 1.0 | Working-group draft |
| AuthZEN Profile for Obligations 1.0 | Working-group draft: mandatory, machine-readable actions a PDP attaches to a decision |