| coaz-mcp | February 2026 | |
| Tulshibagwale & Olivier | Standards Track | [Page] |
This specification defines COAZ-MCP, the COAZ binding — as defined by the COAZ Framework [COAZFW] — for the Model Context Protocol (MCP) [MCP]. It defines how MCP JSON-RPC messages are mapped into requests to the OpenID AuthZEN Authorization API [AUTHZEN], enabling MCP gateways and servers to perform fine-grained, parameter-level authorization through an AuthZEN Policy Decision Point (PDP). The binding defines a fixed default mapping for each MCP method, so that all MCP messages can be authorized without per-operation configuration, and allows MCP servers to override the default for a specific tool by declaring a mapping in the tool's input schema using Common Expression Language (CEL) [CEL]. It also defines how an MCP server advertises declared mappings so that clients can understand how authorization will be performed.¶
The Model Context Protocol [MCP] enables AI agents (MCP clients) to discover and invoke capabilities offered by MCP servers — tools, resources, prompts, and more — over JSON-RPC [JSONRPC]. Authorization in MCP relies on OAuth 2.1 [OAUTH21], but OAuth alone leaves some concerns unaddressed:¶
The access token may carry scopes, but the resources a given call is actually permitted to touch depend on the user and on dynamic, fine-grained policy that scopes cannot express.¶
If the OAuth access token is issued to the agent — which may act autonomously on behalf of a human user who is not present, or with a human in the loop — there is no standard mechanism to capture, per request, the identity of the user on whose behalf the agent acts as distinct from the agent itself.¶
The OpenID AuthZEN Authorization API [AUTHZEN] provides standardized, fine-grained authorization using the Subject-Action-Resource-Context (SARC) model. The COAZ Framework [COAZFW] defines, in a protocol-neutral way, how to project the information model of a protocol into an AuthZEN Authorization API request. This specification is COAZ-MCP, the COAZ binding for MCP: it binds that framework to MCP's information model.¶
This binding authorizes all MCP messages. It does so by defining a fixed default mapping for each MCP method (Section 7), which a PEP applies unless an MCP server has declared a more specific mapping for a particular tool (Section 8).¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This specification uses the terms defined in the COAZ Framework [COAZFW] — notably binding, information model, input variable, mapping, literal, expression, default mapping, declared mapping, PEP, and PDP — and adds the following:¶
An AI agent or application that connects to MCP servers and invokes their capabilities. The MCP Client acts as the caller.¶
A service that exposes tools, resources, and prompts to MCP clients via the Model Context Protocol.¶
An optional intermediary between an MCP client and one or more MCP servers. Where present, a gateway MAY act as the PEP on behalf of the MCP server.¶
A callable capability exposed by an MCP server, described by a name, description, and input schema.¶
Compatible with OpenID AuthZEN (pronounced "cozy"). The framework [COAZFW] of which this specification is a binding.¶
This binding fulfils the binding conformance requirements of the COAZ Framework [COAZFW] as summarized below; each row is specified in the referenced section.¶
| Framework requirement | This binding |
|---|---|
| Information model |
params, token (Section 3) |
| Mapping location |
x-authzen-mapping in a tool's inputSchema; otherwise the default mapping (Section 8, Section 7) |
| Literal/expression discriminator | framework default: $ prefix, $$ escape (Section 4) |
| Expression language | framework default: Common Expression Language [CEL] (Section 4) |
| Envelopes |
evaluation and evaluations (Section 5) |
| Operations in scope | All MCP methods, per the default mapping table; pass-through set listed (Section 7) |
| Default mapping behavior | Per-method default mapping table (Section 7) |
| Declared mapping behavior | MCP server MAY declare a mapping for a tool; overrides the default for that tool (Section 8) |
| Trust-anchored fields |
subject.id SHOULD be anchored to the subject-identity claim; when it is, it is enforced by verification against $token.sub, and a mapping MAY override its source for edge cases (Section 8) |
| Error transport | JSON-RPC 2.0 error responses (Section 10) |
| Discoverability | Declared mappings advertised in the tools/list response (Section 6) |
The response to tools/list carries each tool's inputSchema, and, for tools
that declare a mapping, the x-authzen-mapping within it. Because the mapping
travels in the protocol itself, it reaches every party that can act as the PEP.
This binding supports both deployment shapes:¶
Gateway as PEP. Where an MCP gateway sits between clients and servers, it
obtains each tool's declared mapping by observing the tools/list response
it proxies, with no out-of-band configuration. This is what lets a gateway —
often operated by a platform or IT team with no knowledge of a specific
server's business logic — enforce authorization that the server itself
defined: the MCP server declares how its operations are to be authorized,
and the gateway enforces it.¶
Server as PEP. Where no gateway is present, the MCP server enforces its own declared mappings, and the default mappings for everything else, calling the PDP directly.¶
In both shapes, carrying the mapping in tools/list also makes it available to
the MCP client, so that the client (or the LLM driving it) can understand how a
call will be authorized and shape tool arguments appropriately.¶
When an MCP message is processed, the PEP — the MCP gateway or the MCP server itself — selects the applicable mapping (declared, if present for the tool; otherwise the default mapping for the method), constructs the corresponding AuthZEN request, and calls the PDP before allowing the message to take effect.¶
+-------------+ +-------------+ +-------------+ +-------------+
| MCP Client | | MCP Gateway | | MCP Server | | AuthZen PDP |
+-------------+ +-------------+ +-------------+ +-------------+
| | | |
| 1. tools/list | 1. tools/list | |
+------------------->+--------------------->+ |
| | tools incl. any | |
| tools incl. any | x-authzen-mapping | |
| x-authzen-mapping +<---------------------+ |
+<-------------------+ | |
| | | |
| 2. MCP request | 3. authorize | |
+------------------->+----------------------+------------------->|
| | | permit / deny |
| +<---------------------+--------------------+
| | 4. forward request | |
| +--------------------->| (5. server MAY |
| | | re-authorize) |
| | 6. response | |
| 6. response +<---------------------+ |
+<-------------------+ | |
v v v v
Steps 3 and 5 are alternatives. If a gateway acts as the PEP it calls the PDP; otherwise the server does. It is possible, but redundant, for both to call the PDP.¶
This binding exposes two input variables to expressions:¶
params:A map corresponding to the params object of the MCP JSON-RPC request being
authorized. For tools/call this includes name (the tool name) and
arguments (the caller-supplied values); for other methods it contains that
method's parameters as defined by [MCP]. Fields are accessed using standard
CEL field or index notation (e.g., params.name, params.arguments.id,
params.uri, or params.arguments["customer-id"]).¶
token:A map corresponding to the complete set of decoded claims of the JWT-formatted
[RFC7519] OAuth access token used to authorize the request. All claims are
available, including but not limited to sub, iss, aud, exp, and
client_id. Claims are accessed using standard CEL field or index notation
(e.g., token.sub, token.aud, token.client_id).¶
The AuthZEN subject identifies the principal on whose behalf authorization is
requested. Throughout this binding, subject.id is derived from a single token
claim, the subject-identity claim, which by convention is sub — hence the
expression $token.sub used in the mappings below.¶
Where an access token is issued with the agent as the principal (so sub
identifies the agent, not the human user on whose behalf it acts), a deployment
MAY designate a different claim — an on-behalf-of claim — as the subject-identity
claim, so that subject.id carries the human user. When a deployment designates
an on-behalf-of claim C, every use of $token.sub as subject.id in this
binding (in default mappings, in declared mappings, and in the verification of
Section 8) is read as $token.C. The agent identity remains in
context.agent (typically $token.?client_id) regardless. The designated claim
MUST be agreed between the PEP and the token issuer; absent any designation, the
subject-identity claim is sub.¶
This binding uses the framework's defaults unchanged ([COAZFW]): expressions
are written in Common Expression Language [CEL], and the leading-$
discriminator with its $$ escape distinguishes them from literals. A string
value beginning with $ is a CEL expression evaluated against the params and
token input variables (e.g., $token.sub, $params.arguments.id,
$params.arguments.amount > 10000 ? 'high' : 'standard'); any other value is a
literal, used verbatim (e.g., the string customer, the number 10, the
boolean true); $$50 denotes the literal string $50. Within an expression,
ordinary CEL syntax applies, including CEL's own single-quoted string literals.¶
Per the COAZ expression contract ([COAZFW]), a CEL expression MUST evaluate
to a single JSON value — a scalar, list, or map. A list or map returned by an
expression is a single field value and does not, by itself, produce multiple
evaluations. To populate an OPTIONAL field from a claim or parameter that may
be absent, an expression MUST use CEL optional selection (the .? operator,
e.g. $token.?client_id), which omits the field from the request when the key
is missing; plain field selection on a missing key is an evaluation error.¶
An expression whose evaluation results in an error, or that yields absent or
null for a REQUIRED field (subject, action, or resource), is a mapping
error (Section 10.1). Every mapping in this binding — including every
default mapping — contains CEL expressions, so a conforming PEP MUST include a
CEL evaluator; there is no expression-free conformance level.¶
The following example illustrates how the input variables are populated. Given
the tools/call request:¶
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_customer",
"arguments": { "id": "cust-12345", "case": "case-67890" }
}
}
and an access token with decoded claims:¶
{
"sub": "alice@example.com",
"client_id": "http://agentprovider.com/agent-app-id",
"iss": "https://auth.example.com",
"aud": "https://mcp.example.com",
"exp": 1750000000
}
expressions resolve as follows:¶
| Expression | Resolved value |
|---|---|
$params.name
|
"get_customer"
|
$params.arguments.id
|
"cust-12345"
|
$token.sub
|
"alice@example.com"
|
$token.?client_id
|
"http://agentprovider.com/agent-app-id"
|
customer
|
"customer" (literal) |
As defined by the COAZ Framework ([COAZFW]), a mapping is a JSON object with a single top-level member — its envelope — whose key names the AuthZEN API to call. This binding permits both envelope keys defined by the framework:¶
evaluation — a template for a single-decision Access Evaluation request.
Every default mapping in this binding uses this envelope
(Section 7), and it is the RECOMMENDED envelope for declared
mappings that require one decision.¶
evaluations — a template for a multi-decision Access Evaluations request,
for tools whose single invocation requires more than one decision (see
Figure 7).¶
A mapping whose top-level structure is anything other than exactly one of these two keys is malformed, and the PEP MUST treat it as a mapping error (Section 10.1).¶
A PDP used with this binding MUST support the Access Evaluation API. Where a
declared mapping uses the evaluations envelope, the PEP MUST either send the
constructed request to the Access Evaluations API or, if its PDP does not
support that API, issue one Access Evaluation request per entry — applying the
top-level defaults to each entry exactly as [AUTHZEN] defines — and allow the
message only if every decision is a permit.¶
An MCP server declares a mapping for a tool by including an x-authzen-mapping
object within that tool's inputSchema in the tools/list response. The
presence of x-authzen-mapping indicates the tool carries a declared mapping;
its absence means the default mapping for tools/call applies. No separate
marker field is used.¶
Because the declared mapping is carried in the tools/list response, it is
available to any MCP gateway on the path — which can therefore enforce it as
the PEP without out-of-band configuration (Section 2) — and to
the MCP client, satisfying the framework's discoverability capability. When MCP
Server Cards become available, x-authzen-mapping SHOULD also be included
there.¶
The following non-normative example shows a tools/list response with one tool
that declares a mapping and one that does not:¶
{
"tools": [
{
"name": "get_customer",
"description": "Get customer details",
"inputSchema": {
"type": "object",
"properties": {
"id": { "type": "string", "description": "The customer identifier" },
"case": { "type": "string", "description": "The case being worked on" }
},
"required": ["id"],
"x-authzen-mapping": {
"evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"action": { "name": "get_customer" },
"resource": { "type": "customer", "id": "$params.arguments.id" },
"context": { "agent": "$token.?client_id", "case": "$params.arguments.case" }
}
}
}
},
{
"name": "get_local_weather",
"description": "Get weather for the local area",
"inputSchema": {
"type": "object",
"properties": { "zip": { "type": "string", "description": "Zip code" } },
"required": ["zip"]
}
}
]
}
get_customer declares a mapping; get_local_weather does not and is therefore
authorized by the default tools/call mapping (Section 7).¶
This binding defines a default mapping for each MCP method. A PEP MUST apply the default mapping for a method unless a declared mapping applies to the specific operation (Section 8).¶
Every default mapping uses the evaluation envelope (Section 5)
and shares the following subject and context, which establish the subject
(the user on whose behalf the agent acts) and the agent context:¶
{
"evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "...": "per-method" },
"resource": { "...": "per-method" }
}
}
For methods that target the MCP server as a whole rather than a specific item,
resource.id identifies this MCP server. MCP requires access tokens to be
audience-bound to the target server via resource indicators [RFC8707], so the
server identity is carried in the aud claim. The default mappings express this
as $token.aud, with the following normative resolution: resource.id MUST be a
single string identifying this server. When aud is a single string, that value
is used directly. When aud is an array (as permitted by [RFC7519]), the PEP
MUST select the single member that identifies this server (its own resource
identifier per [RFC8707]) and use that value; resource.id MUST NOT be set to
an array. If the PEP cannot resolve aud to exactly one server-identifying
value, it is a mapping error (Section 10.1). A PEP that knows its own
resource identifier from configuration MAY use it directly instead of deriving
it from aud.¶
The default mappings are:¶
// initialize
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id", "protocol_version": "$params.protocolVersion" },
"action": { "name": "initialize" },
"resource": { "type": "mcp_server", "id": "$token.aud" } } }
¶
ping is a pass-through operation: the PEP MUST NOT call the PDP and MUST allow
it to proceed.¶
// tools/list
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "tools/list" },
"resource": { "type": "mcp_server", "id": "$token.aud" } } }
// tools/call (applies when the tool declares no mapping)
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "tools/call" },
"resource": { "type": "tool", "id": "$params.name" } } }
¶
// resources/list
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "resources/list" },
"resource": { "type": "mcp_server", "id": "$token.aud" } } }
// resources/read (resources/subscribe and resources/unsubscribe use the
// same shape; only action.name differs)
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "resources/read" },
"resource": { "type": "resource", "id": "$params.uri" } } }
¶
// prompts/list
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "prompts/list" },
"resource": { "type": "mcp_server", "id": "$token.aud" } } }
// prompts/get
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "prompts/get" },
"resource": { "type": "prompt", "id": "$params.name" } } }
¶
// completion/complete
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "completion/complete" },
"resource": {
"type": "$params.ref.type == 'ref/prompt' ? 'prompt' : 'resource'",
"id": "$params.ref.type == 'ref/prompt' ? $params.ref.name : $params.ref.uri" } } }
¶
// logging/setLevel
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id", "level": "$params.level" },
"action": { "name": "logging/setLevel" },
"resource": { "type": "mcp_server", "id": "$token.aud" } } }
¶
// tasks/get (tasks/result and tasks/cancel use the same shape; only
// action.name differs)
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "tasks/get" },
"resource": { "type": "task", "id": "$params.taskId" } } }
// tasks/list
{ "evaluation": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"action": { "name": "tasks/list" },
"resource": { "type": "mcp_server", "id": "$token.aud" } } }
¶
The following are pass-through: the PEP MUST NOT call the PDP for them and MUST allow them to proceed. They are listed explicitly so that the absence of a mapping is never interpreted as a deny.¶
A request whose method has neither a default mapping defined by this binding
nor an applicable declared mapping, and that is not in the pass-through set
above, MUST be denied: the PEP MUST NOT allow it to proceed and MUST return an
authorization denial (Section 10.2). This ensures that methods
introduced by future MCP versions or extensions fail closed rather than bypassing
authorization. The pass-through set is the only set of methods that proceed
without a PDP decision.¶
The MCP requests sampling/createMessage, elicitation/create, and
roots/list are initiated by the server toward the client. The PEP model in
this binding authorizes client-to-server requests using the client's access
token, which is not the appropriate identity for server-initiated requests.
Authorization of server-initiated requests is therefore out of scope for this
version of the binding.¶
An MCP server MAY declare a mapping for a tool by including x-authzen-mapping in
the tool's inputSchema (Section 6). A declared mapping has the same
shape as a default mapping — an envelope naming the AuthZEN API and a template
for that API's request body (Section 5) — and uses the same
expression and literal rules (Section 4). A declared mapping MAY use
either the evaluation or the evaluations envelope: evaluation suffices
for most tools, while evaluations serves tools whose single invocation
requires multiple decisions.¶
A declared mapping for a tool overrides the default tools/call mapping for
that tool only. It does not affect the default mapping for any other method or
for tools/call of any other tool.¶
The subject identifier (subject.id) SHOULD be anchored to the subject-identity
claim of the validated access token. A declared mapping SHOULD include a
subject — the request's subject under the evaluation envelope, or the
top-level subject under the evaluations envelope — whose id is set to an
expression resolving to the subject-identity claim (Section 3.1)
— that is, $token.sub (or $token.C where an on-behalf-of claim C is
designated). Where subject.id is set to the subject-identity claim, the PEP
MUST verify that its resolved value equals that claim in the validated access
token, treating a mismatch as a mapping error (Section 10.1); this is the
trust-anchored, verification-enforced case defined by the COAZ Framework
([COAZFW]), and it prevents an MCP server — the party being authorized — from
asserting the identity of a different subject.¶
Some deployments cannot convey the subject identity in a token claim — for
example, an agentic deployment whose access token is issued to the agent while
the identity of the acting user is carried outside the token. For these edge
cases a declared mapping MAY override subject.id with a value derived from
elsewhere in the operation's inputs; because full override via a CEL expression
is already available, any field of the incoming request — token claim, request
parameter, or otherwise — can be mapped to subject.id. An overridden
subject.id SHOULD still be derived from the validated token rather than from
caller-controlled request parameters. When a declared mapping sets subject.id
from a source the PEP cannot verify against a token claim, the subject identity
is asserted by the mapping author rather than anchored to the token; a PEP —
particularly a gateway enforcing a mapping authored by an MCP server — SHOULD
emit a warning in this case, and deployments SHOULD account for the additional
trust this places in the mapping author (see Section 11).¶
If a declared mapping omits subject or subject.id, the PEP MUST supply the
default subject identifier, $token.sub (or $token.C), so that every request
still carries a token-anchored subject.¶
To close off identity smuggling, a declared mapping using the evaluations
envelope MUST NOT set subject within any entry of its evaluations array;
the single top-level subject applies to all evaluations. A PEP MUST reject a
declared mapping that places subject inside an evaluations entry as a
mapping error.¶
A declared mapping MAY otherwise shape the subject: it MAY set
subject.type and additional subject attributes (under properties, per
[AUTHZEN]), so that different invocations can produce different subject objects.
If a declared mapping omits subject.type, the PEP MUST supply identity. Any
subject attribute other than subject.id — including subject.type — is
untrusted input, exactly like declared action, resource, and context
attributes; only the verified subject.id is trustworthy as the authenticated
identity. See Section 11.¶
For autonomous-agent use cases the context MUST include the agent identity
(typically $token.?client_id).¶
The declared mapping for get_customer in Figure 4 uses the
evaluation envelope. Applied to the request and token in Figure 2
and Figure 3, it produces the following Access Evaluation request,
sent to the Access Evaluation API:¶
{
"subject": { "type": "identity", "id": "alice@example.com" },
"action": { "name": "get_customer" },
"resource": { "type": "customer", "id": "cust-12345" },
"context": { "agent": "http://agentprovider.com/agent-app-id", "case": "case-67890" }
}
A tool that copies an object requires two checks: read on the source and
write on the destination. The mapping therefore uses the evaluations
envelope: the two checks are entries in the evaluations array, and the shared
subject and context remain at the top level of the request body. As in
every declared mapping, subject.id is set to $token.sub and verified by the
PEP.¶
{
"name": "copy_object",
"description": "Copy a storage object from one location to another",
"inputSchema": {
"type": "object",
"properties": {
"source": { "type": "string", "description": "Source object location" },
"destination": { "type": "string", "description": "Destination object location" }
},
"required": ["source", "destination"],
"x-authzen-mapping": {
"evaluations": {
"subject": { "type": "identity", "id": "$token.sub" },
"context": { "agent": "$token.?client_id" },
"evaluations": [
{ "action": { "name": "read" },
"resource": { "type": "storage_object", "id": "$params.arguments.source" } },
{ "action": { "name": "write" },
"resource": { "type": "storage_object", "id": "$params.arguments.destination" } }
]
}
}
}
}
For a tools/call to copy_object with arguments
{"source": "/bucket/reports/q1.pdf", "destination": "/bucket/archive/q1.pdf"},
the resulting Access Evaluations request, sent to the Access Evaluations API,
is:¶
{
"subject": { "type": "identity", "id": "alice@example.com" },
"context": { "agent": "http://agentprovider.com/agent-app-id" },
"evaluations": [
{ "action": { "name": "read" },
"resource": { "type": "storage_object", "id": "/bucket/reports/q1.pdf" } },
{ "action": { "name": "write" },
"resource": { "type": "storage_object", "id": "/bucket/archive/q1.pdf" } }
]
}
CEL conditionals and built-ins MAY be used in any expression. A transfer_funds
tool derives values from arguments and token claims, including a subject.type
that varies with the caller's roles. The mapping sets subject.id to $token.sub,
which the PEP verifies against the token (Section 8). Because the
declared subject.type is untrusted, a policy that grants elevated access MUST do
so on the basis of the verified subject.id, not the declared subject.type:¶
{
"name": "transfer_funds",
"description": "Transfer funds between accounts",
"inputSchema": {
"type": "object",
"properties": {
"from_account": { "type": "string" },
"to_account": { "type": "string" },
"amount": { "type": "number" },
"currency": { "type": "string" }
},
"required": ["from_account", "to_account", "amount", "currency"],
"x-authzen-mapping": {
"evaluation": {
"subject": {
"type": "$token.roles.exists(r, r == 'treasury') ? 'treasury_user' : 'standard_user'",
"id": "$token.sub"
},
"action": { "name": "$params.arguments.currency == 'USD' ? 'domestic_transfer' : 'international_transfer'" },
"resource": {
"type": "account",
"id": "$params.arguments.from_account",
"properties": {
"sensitivity": "$params.arguments.amount > 10000 ? 'high' : 'standard'"
}
},
"context": { "agent": "$token.?client_id", "target_account": "$params.arguments.to_account" }
}
}
}
}
When an MCP message is processed, the PEP MUST:¶
Determine the JSON-RPC method. If it is a pass-through operation
(Section 7), allow it without calling the PDP.¶
Select the applicable mapping: for a tools/call whose tool declares an
x-authzen-mapping, use the declared mapping; otherwise use the default mapping
for the method. A method that is neither in the pass-through set nor has a
mapping MUST be denied (Section 7).¶
Populate params from the request's params object and token from the
decoded, validated access token claims.¶
Resolve the mapping: literals verbatim, $-prefixed values by evaluating the
CEL expression (Section 4).¶
Anchor the subject identity: where a mapping sets subject.id to the
subject-identity claim (Section 3.1) — as every default mapping
does — the effective subject.id of every decision in the constructed request
— the request's subject under the evaluation envelope; the top-level
subject and, after any override, the subject of each evaluations entry under
the evaluations envelope — MUST equal the value of that claim in the validated
access token, and the PEP MUST treat a mismatch as a mapping error
(Section 10.1) and not call the PDP. Where a declared mapping instead
overrides subject.id from another source (Section 8), the PEP
cannot perform this verification and SHOULD emit a warning. A declared mapping
MUST NOT carry a per-evaluation subject; if one is present, treat the request
as a mapping error and do not call the PDP.¶
Construct the AuthZEN request from the resolved mapping and send it to the
API named by the mapping's envelope (Section 5): the Access
Evaluation API for evaluation, the Access Evaluations API for
evaluations.¶
Enforce the response: if every decision is true (permit), allow the message
to proceed; if any decision is false (deny), do not allow it and return a
JSON-RPC error (Section 10.2).¶
This binding reports the COAZ denial and error categories ([COAZFW]) as
JSON-RPC 2.0 [JSONRPC] error responses. In every case the PEP MUST NOT allow
the message to proceed. As required by [JSONRPC], the id of each error
response MUST equal the id of the request that caused it, or be null if the
request id could not be determined.¶
A mapping error occurs when the PEP cannot construct a valid AuthZEN request —
for example, an expression references a missing field, an expression fails to
evaluate, or the mapping is malformed. The PEP MUST return error code -32602
(Invalid params). The message SHOULD describe the failure.¶
{
"jsonrpc": "2.0",
"id": 456,
"error": {
"code": -32602,
"message": "COAZ mapping error: expression '$params.arguments.region' failed: no such key 'region'"
}
}
If the PEP cannot reach the PDP or receives an invalid response, it MUST return
error code -32603 (Internal error).¶
{
"jsonrpc": "2.0",
"id": 101,
"error": { "code": -32603, "message": "Authorization service unavailable" }
}
The access token referenced by the token input variable MUST be validated by
the PEP before its claims are used. The PEP MUST verify the token signature,
issuer, audience, and expiration in accordance with [RFC7519] and the OAuth
framework in use. Because default mappings for server-scoped operations use the
aud claim as the resource identifier, correct audience validation and
resource-indicator binding [RFC8707] are essential.¶
This binding represents the human user as the AuthZEN Subject and the AI agent as part of the Context. This separation lets policies evaluate the trust level of the user and the agent independently, supporting zero-trust architectures for AI agent interactions.¶
The subject identifier (subject.id) SHOULD be anchored to the subject-identity
claim of the validated access token. Every default mapping sets it to $token.sub,
and where a declared mapping sets it to the subject-identity claim the PEP verifies
that the resolved value equals the subject of the validated access token, rejecting
any mismatch (Section 8). This verification is essential where the PEP
is an MCP gateway and the declared mapping is authored by the MCP server: without
it, the server — the party being authorized — could assert the identity of any
principal and obtain a decision for a user the caller never authenticated as.¶
This binding uses SHOULD rather than MUST so that agentic deployments whose
acting-user identity is not carried in a verifiable token claim can still map a
subject.id (Section 8). This flexibility is a deliberate trade-off:
a subject.id that the PEP cannot verify against a token claim is only as
trustworthy as the mapping author and the source it is drawn from. Deployments
SHOULD prefer token-anchored subject identities, SHOULD surface a warning when a
mapping overrides subject.id from an unverifiable source, and MUST weigh, in
their threat model, the trust placed in whoever authored such a mapping. Where the
PEP is a gateway enforcing a mapping authored by a less-trusted MCP server, an
unverifiable subject.id SHOULD NOT be relied upon as an authenticated identity.¶
A declared mapping is supplied by the MCP server, which is the party whose
operation is being authorized. Every attribute it produces — action,
resource, context, and all subject attributes other than the verified
subject.id, including subject.type — is untrusted input to the PDP. PDP
policies MUST NOT treat an attribute that originates from a declared mapping as
authoritative for identity or privilege; for example, a policy MUST NOT grant
access on the basis of a subject.type or context attribute that the mapping
could freely set. The trustworthy authorization inputs are the verified
subject.id and any attributes the PDP itself obtains from trusted sources.¶
A declared mapping is supplied by the MCP server. A PEP SHOULD validate that a
declared mapping is well-formed and that its expressions reference only defined
properties, and MUST verify the trust-anchored subject.id (Section 8),
before relying on it. The trust placed in the server as the author of declared
mappings MUST be considered in the deployment's threat model.¶
As required by the framework, the PEP MUST fail closed: a mapping error, a denial, or a PDP communication failure all result in the message being refused.¶
All communication between the PEP and the PDP MUST use TLS, as specified in the transport requirements of [AUTHZEN].¶
Implementers SHOULD ensure that at least one COAZ-aware PEP in the deployment path authorizes each in-scope MCP message. If no PEP processes the mapping, no AuthZEN authorization occurs and access control falls back to other mechanisms.¶
This specification has no IANA actions.¶
This specification is a binding of the COAZ Framework [COAZFW]. The framework defines the protocol-neutral model — mappings shaped as AuthZEN Access Evaluations requests, the literal/expression distinction, the expression contract, and the conformance requirements. This binding binds that model to MCP's information model and JSON-RPC transport.¶
This binding extends the MCP [MCP] tool schema with the x-authzen-mapping
extension to inputSchema. It is backward compatible: servers and clients that
do not understand it ignore the field, and the default mappings still allow a
PEP to authorize their messages.¶
The authors would like to thank Martin Besozzi for the original proposal on AuthZEN integration for fine-grained authorization in MCP, and the members of the OpenID AuthZEN Working Group for their ongoing work on the Authorization API standard.¶
Copyright (c) 2025 The OpenID Foundation.¶
The OpenID Foundation (OIDF) grants to any Contributor, developer, implementer, or other interested party a non-exclusive, royalty free, worldwide copyright license to reproduce, prepare derivative works from, distribute, perform and display, this Implementers Draft, Final Specification, or Final Specification Incorporating Errata Corrections solely for the purposes of (i) developing specifications, and (ii) implementing Implementers Drafts, Final Specifications, and Final Specification Incorporating Errata Corrections based on such documents, provided that attribution be made to the OIDF as the source of the material, but that such attribution does not indicate an endorsement by the OIDF.¶
The technology described in this specification was made available from contributions from various sources, including members of the OpenID Foundation and others. Although the OpenID Foundation has taken steps to help ensure that the technology is available for distribution, it takes no position regarding the validity or scope of any intellectual property or other rights that might be claimed to pertain to the implementation or use of the technology described in this specification or the extent to which any license under such rights might or might not be available; neither does it represent that it has made any independent effort to identify any such rights. The OpenID Foundation and the contributors to this specification make no (and hereby expressly disclaim any) warranties (express, implied, or otherwise), including implied warranties of merchantability, non-infringement, fitness for a particular purpose, or title, related to this specification, and the entire risk as to implementing this specification is assumed by the implementer. The OpenID Intellectual Property Rights policy (found at openid.net) requires contributors to offer a patent promise not to assert certain patent claims against other contributors and against implementers. OpenID invites any interested party to bring to its attention any copyrights, patents, patent applications, or other proprietary rights that may cover technology that may be required to practice this specification.¶