The wire

MCP binding

How the grant travels over Model Context Protocol — discovery channels, both challenge encodings, the pend, and what MCP's types cannot yet express.

MCP carries the challenge and the eventual call. It does not carry the grant — beats two through four happen at the owner's authorization server over HTTP, unchanged. That separation is what lets one grant serve multiple bindings.

The repository's draft is docs/MCP-BINDING.md.

Discovery: three channels, one registry

A client can learn that it must negotiate, and where, before its first call. All three channels are generated from the same tool registry.

Channel Nature Notes
RFC 9728 Protected Resource Metadata fetched Mandatory for MCP servers since 2026-07-28
AAuth resource metadata fetched The same structural facts under a content-addressed R3 vocabulary
capabilities.extensions negotiated Arrives in the handshake the client was already doing

The third is new here. A resource enforcing in-process advertises the extension in its server/discover response:

"extensions": {
  "dev.uma4agents/uma-enforcement": {
    "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
    "authorization_servers": ["https://alice-as.uma.lab"],
    "challenge": {"jsonrpc_error_code": -32001}
  }
}

No extra round trip and no well-known URI.

Use server/discover, not initialize. The 2026-07-28 revision is unreachable through the legacy handshake by construction — the reference SDK separates handshake protocol versions (topping out at 2025-11-25) from modern ones. A client that asks initialize for 2026-07-28 is answered 2025-11-25 and nothing appears wrong. Assert the negotiated version rather than assuming it.

The challenge, in two encodings

The parameters are fixed: realm, error, as_uri, ticket, resource_metadata, scope, authorization_remediation. How they travel depends on whether the enforcement point has a status line.

Gateway host — an external authorization service ahead of the resource:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: UMA realm="alice-vault", error="insufficient_authorization",
  as_uri="https://alice-as.uma.lab", ticket="tkt_…",
  resource_metadata="https://gateway.uma.lab/.well-known/oauth-protected-resource/mcp",
  scope="trades:execute", authorization_remediation="<base64url JSON>"

In-process host — an MCP extension, no gateway in the path, no status line to decorate:

{"jsonrpc": "2.0", "id": 4, "error": {
  "code": -32001,
  "message": "authorization required: present this ticket to the resource owner's AS",
  "data": {"error": "uma_challenge", "as_uri": "https://alice-as.uma.lab",
           "ticket": "tkt_…", "resource_metadata": "…", "realm": "alice-vault"}}}

A client should accept both. The authorization_remediation object is byte-for-byte identical in each, which is the point of carrying it: the RAR-metadata draft defines that payload against WWW-Authenticate, and this shows the payload survives a transport with no status line.

A gateway-hosted enforcement point can never answer beat one with a JSON-RPC result. In an external-authorization callout a 2xx means allow and the body is discarded; only non-2xx bodies reach the client. Any design where a gateway-hosted enforcement point returns a typed result rather than an error is impossible, which is why the challenge is an error in both encodings.

The pend

When the owner's decision is outstanding, a requesting side that can render a wait should hand it up rather than hold the call open. On 2026-07-28 the SDK turns a server-side elicitation into a result carrying a request_state handle, so the call suspends and resumes instead of hanging.

That machinery is right. The type is not:

input_requests: dict[str, CreateMessageRequest | ListRootsRequest | ElicitRequest]

All three address the client's own user — its model, its filesystem, its human. There is no member for blocked on a different principal, who is not on this connection and cannot be reached through it.

This implementation does not emit a subject block. The shim asks the agent's human the only question that is genuinely theirs — keep waiting, or stop — and describes the owner's role in prose. That is the honest ceiling of what the current type allows.

The proposal adds a subject whose one required field is reachable_by_client:

"subject": {
  "party": "resource_owner",
  "is_requesting_party": false,
  "reachable_by_client": false,
  "notified": true
}

Without it, a conforming client reading only the typed structure sees an elicitation and tries to satisfy the wait from its own user, who has no part in the decision.

Routing headers, and a new confusion class

Mcp-Method and Mcp-Name are required on 2026-07-28 and let an enforcement point route and decide without parsing a body. They also let it be steered: send Mcp-Method: tools/list — open — with a tools/call body — protected — and a header-trusting enforcement point waves it through to a resource that dispatches on the body.

Two parsers over one message is the request-smuggling shape, arriving in a new protocol.

Any enforcement point reading these headers must reconcile them against the body, and should require them on protected methods rather than merely checking them when present. An absent header is as steerable as a lying one. The reference SDK rejects both cases for mcp-name, which is corroboration that the concern is real; the specification does not currently say so.

Step-up remediation

SEP-2643 applies the RAR-metadata pattern to MCP: when a call fails for want of authority, the server returns machine-readable guidance instead of leaving the client to construct a step-up request from documentation. The challenge above is that guidance, plus two parameters.

RAR-metadata / SEP-2643 U4A
Payload authorization_details array the same array, plus authorization_server and ticket
Nature a template the client fills in and submits a handle to a negotiation already open
Who authors the request the client nobody — the resource registered it, the owner's authority decides
Whose authorization server the client's own the resource owner's
Server-side state none by design a negotiation, which is what makes a pend possible

The step that does not survive the move to a third-party owner is the client takes the template to its authorization server. The agent's authorization server cannot grant access to somebody else's vault. Naming authorization_server in the remediation object, and returning a ticket rather than a template, is what makes the same failure decidable by a party who is not the caller — a small enough addition that it belongs in that draft rather than in a rival one.

Statelessness costs the pend too. A template has nowhere to hold "the owner has been asked and has not answered yet."

Tasks: not implemented, and why

The resumable-request machinery already carries the pend, so Tasks would add durable server-side handles for the case where the owner is asleep for hours rather than seconds. It is explicitly future here rather than stubbed — tasks/* is refused with unknown_method rather than half-answered.

Two things should be settled first.

taskId should not be a bearer token. SEP-2663 says it cannot scope tasks/list because servers cannot reliably correlate two unrelated handles to the same caller, and settles for unguessable ids. This profile does not need to correlate handles: authority to observe or act on a negotiation is a signature verifying against the key that signed the agreement, over a single-use rotating ticket. With that, taskId can be a stable public identifier and tasks/list becomes scopeable — negotiations bound to the key that signed this request.

A task here augments the authorization decision, not the work. SEP-2663 assumes a task wraps the tool call, so completed means the tool finished. A gateway-hosted enforcement point structurally cannot observe the tool result. A taskKind: "authorization" would let completed honestly mean the decision resolved — retry your original request.

One refinement for any implementation: tasks/get should verify the ticket without consuming it. Single-use exists to stop a replayed ticket escalating into a grant; a read-only observation advances no state, and a consuming poll makes a lost response brick the negotiation.

Conformance summary

Mechanism Status
server/discover, stateless transport, _meta client identity implemented
RFC 9728 PRM, AAuth R3 metadata, capabilities.extensions implemented
Challenge in both encodings; one client understands both implemented
Pend hand-back with request_state implemented
Mcp-Method / Mcp-Name reconciliation, required on protected methods implemented
Origin validation implemented
RAR-metadata remediation in the challenge, both encodings implemented
authorization_server + ticket inside the remediation object proposed
subject on an input request proposed
Tasks, taskKind, proof-of-possession on tasks future

What's next