← Blog
July 27, 2026 · 12 min

Enterprise-Managed Authorization: ID-JAG and the IdP Inside MCP

In consumer MCP, the consent screen is a feature: you decide what touches your data. In an enterprise, it is a liability multiplied by headcount. The Enterprise-Managed Authorization extension — now stable — deletes the consent screen entirely and hands the decision to the corporate identity provider.

When we walked the MCP authorization spec two weeks ago, the model was clean: a protected MCP server is an OAuth 2.1 resource server, the client discovers the authorization server through a 401, and a human clicks "allow" somewhere in a browser. That last step is the problem this post is about. An organization with two thousand employees and thirty approved MCP servers is looking at sixty thousand individual authorization ceremonies — each one a phishing surface, each one invisible to the security team, and every offboarding a scavenger hunt across every server the ex-employee ever approved.

The Enterprise-Managed Authorization extension (identifier io.modelcontextprotocol/enterprise-managed-authorization, EMA for short) replaces all of that with a single primitive: the Identity Assertion JWT Authorization Grant, or ID-JAG. The extension reached stable status and was announced on the official MCP blog on June 18, 2026, with Okta, Anthropic's Claude, and VS Code among the first movers. It grew out of SEP-990 ("Enable Enterprise IdP Policy Controls") and lives in the modelcontextprotocol/ext-auth repository on the stable track. This piece walks the protocol as specified, then asks our usual question: what changes for agents that pay per call?

The shape of the fix: authorization moves upstream

The core move is architectural. In the baseline flow, the authorization decision happens at each MCP server's authorization server, one consent at a time. EMA moves that decision upstream into the enterprise IdP — Okta, Entra ID, whatever the org already runs. The IdP holds a registry of approved MCP servers and the policy for each: which groups, which roles, which conditional-access rules. The employee logs in once with corporate SSO. Everything downstream is token plumbing, with no browser involved.

That plumbing is two standard building blocks composed in sequence. First, OAuth Token Exchange (RFC 8693): the client trades its identity assertion at the IdP for an ID-JAG. Second, the JWT Profile for OAuth 2.0 Authorization Grants (RFC 7523): the client presents that ID-JAG to the MCP server's authorization server and receives an access token. No redirect to the resource's authorization endpoint. No authorization code. No consent screen. The authorization decision was already made when the IdP evaluated policy and agreed to mint the grant.

The underlying token format is not an MCP invention. It comes from the IETF OAuth working group draft draft-ietf-oauth-identity-assertion-authz-grant — the spec behind what Okta markets as Cross App Access (XAA). It is a working-group document on the standards track, authored by Aaron Parecki (Okta), Karl McGuinness, and Brian Campbell (Ping Identity); revision -04 is dated May 21, 2026. That matters operationally: the MCP extension is stable, but it sits on a moving IETF draft, so implementers should pin the draft revision they support and track the working group.

Leg one: trading an ID token for an ID-JAG

After SSO, the MCP client holds an identity assertion — an OpenID Connect ID token, or in the SAML case, a refresh token obtained by exchanging the SAML assertion at the IdP first. To reach a specific MCP server, the client sends a token exchange request to the IdP's token endpoint:

POST /oauth2/token HTTP/1.1
Host: idp.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&requested_token_type=urn:ietf:params:oauth:token-type:id-jag
&audience=https://auth.mcp-server.example
&resource=https://mcp-server.example/mcp
&subject_token=eyJraWQi...
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&scope=mcp.tools.read

The parameter semantics are strict. audience MUST be the issuer identifier of the MCP server's authorization server — the party that will consume the grant. resource, if present, MUST be the resource identifier of the MCP server itself. subject_token_type is id_token for OpenID Connect deployments or saml2 for SAML shops. Client authentication (client_id plus a credential) applies when the IdP requires it — and in practice it will, because enterprise IdP policy typically only lets pre-registered clients sign users in at all.

This request is the policy checkpoint. The IdP validates the subject token, confirms it was issued to this client, evaluates policy for this user–client–server combination, and may narrow the requested scopes to whatever policy allows. If the employee is not authorized for that server, the exchange fails and the client never holds a token of any kind for it. If it succeeds, the response carries the ID-JAG — short-lived by design; the spec's worked example uses expires_in: 300, five minutes.

The artifact: what an ID-JAG actually says

The ID-JAG is a signed JWT with the explicit media type oauth-id-jag+jwt in its typ header — a deliberate anti-confusion measure so it can never be mistaken for an ID token or an access token. Its payload looks like this:

{
  "jti": "9e43f81b64a33f20116179",
  "iss": "https://idp.example.com",
  "sub": "U019488227",
  "aud": "https://auth.mcp-server.example",
  "client_id": "f53f191f9311af35",
  "resource": "https://mcp-server.example/mcp",
  "scope": "mcp.tools.read",
  "exp": 1753600000,
  "iat": 1753599700
}

iss, sub, aud, client_id, jti, exp, and iat are required. scope and resource are optional and reflect what the IdP actually granted, not what the client asked for. Two optional claims are worth flagging. authorization_details carries structured, fine-grained authorization requests per RFC 9396 (Rich Authorization Requests) — a JSON array the IdP evaluates and passes through, which we will come back to. And sub_id can carry an alternative subject identifier such as a SAML NameID, for resource servers whose account namespace was built around SAML federation.

Leg two: redeeming the grant

The client now presents the ID-JAG to the MCP server's authorization server as a JWT authorization grant:

POST /oauth2/token HTTP/1.1
Host: auth.mcp-server.example
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=eyJ0eXAiOiJvYXV0aC1pZC1qYWcrand0...
&client_id=https://client.example/oauth-client-metadata

Note the client_id: it can be a Client ID Metadata Document URL, the same CIMD mechanism the core MCP authorization spec leans on instead of dynamic client registration. The authorization server's validation duties are enumerated, and they are where the security of the whole scheme concentrates. It MUST confirm the typ header is oauth-id-jag+jwt; verify the signature against the IdP's published JWKS; check that aud matches its own issuer identifier exactly, rejecting with invalid_grant otherwise; verify the client_id binding against the presenting client; and enforce expiry. If everything holds, it maps sub to a local account and mints its own access token — audience-restricted to the MCP server, with a lifetime it chooses (the spec's example uses 24 hours). The resource side never surrenders token issuance; it surrenders only the consent decision.

Account linking has an explicit rule set: use sub as the primary stable identifier, and fall back to email only for matching pre-existing accounts created before enterprise authorization was configured. Email is mutable and reassignable; subject identifiers are not supposed to be.

Discovery and the MCP packaging

How does a client know a given authorization server speaks this profile? Metadata. A resource authorization server that accepts ID-JAGs advertises urn:ietf:params:oauth:grant-profile:id-jag in the authorization_grant_profiles_supported field of its authorization server metadata. On the IdP side, support for minting the token type is signaled through identity_chaining_requested_token_types_supported. The draft itself notes an open gap for autonomous clients: absent metadata, an agent's only option is to attempt the exchange and see what happens.

On the MCP layer, the packaging follows the extensions mechanism that the 2026-07-28 protocol revision formalized. A client declares support during initialize:

{
  "capabilities": {
    "extensions": {
      "io.modelcontextprotocol/enterprise-managed-authorization": {}
    }
  }
}

Servers that require enterprise-managed auth declare the extension in their authorization metadata, and the client knows not to start a browser-redirect flow it was never going to be allowed to finish. Extensions are opt-in and never active by default.

Who shipped, and what it revoked

The June 18 announcement listed a concrete adoption map. On the IdP side, Okta, via its Cross App Access protocol. On the client side, Claude — Anthropic wired support through the shared MCP layer, so Claude, Claude Code, and Cowork inherit it — and VS Code. On the server side: Asana, Atlassian, Canva, Figma, Granola, Linear, and Supabase, with Slack in progress. Linear's head of engineering Tom Moor put the user-facing effect plainly: "Logging in once and automatically having all your MCP connectors automatically setup is pretty magical." Atlassian published its own engineering write-up on June 29 describing the Rovo MCP implementation, currently in private beta.

The operational win is symmetric to the onboarding win, and arguably bigger. Revocation happens at the IdP: disable the account or pull the group membership, and the next five-minute ID-JAG never gets minted, across every client and every server at once. Compare that to hunting down long-lived per-server OAuth grants one dashboard at a time. Access tokens already issued still run to their expiry — which is exactly why the asymmetry between a five-minute grant and a 24-hour access token is a policy decision each resource server should make consciously, not copy from the example.

What the IdP does not see — the spec is honest about the boundary: the enterprise IdP's visibility is limited to token issuance. It decides who gets in, per client–server pair, per scope. It does not observe the MCP traffic that follows. Auditing tool calls remains the resource server's job — and, for fleets, the gateway's.

The limits that matter for agents

Three constraints in the current drafts deserve attention from anyone running autonomous fleets rather than human employees.

The subject is a user, not an agent. Every claim chain in ID-JAG resolves to an employee identity: sub is the person who did SSO. The draft acknowledges delegation chains — an actor_token parameter and an act claim exist in the token exchange vocabulary — but explicitly declines to define normative processing for them, deferring to future profiles. Until that lands, an agent operating under EMA is indistinguishable from its operator at the identity layer. That is the same gap we flagged in Web Bot Auth, where signatures identify the provider, not the individual agent: the industry keeps standardizing identity one level above where autonomous agents actually live.

Confidential clients only. The draft says this profile SHOULD be limited to confidential clients — public clients stay on the authorization code grant. A headless agent runtime can be a confidential client, but it means credential management is a precondition, not an afterthought.

One grant, one hop. An ID-JAG is bound to a single trust relationship via aud, and the spec prohibits reusing it across downstream hops; each hop in a multi-service chain needs its own IdP-issued grant. For multi-agent systems doing service-to-service fan-out, that means the IdP sits in the loop for every edge of the call graph — a policy feature and a latency tax, simultaneously.

What it means for LLM4Agents

EMA is the procurement gate for enterprise MCP. The clients that matter in corporate deployments — Claude, VS Code — now speak it, and organizations that adopt it will increasingly expect every MCP surface they buy to be governable from the IdP console. LLM4Agents exposes its capabilities as an MCP server; supporting the extension is what makes that surface deployable inside an Okta- or Entra-governed environment without asking anyone's IT department for an exception. The alternative is being the one connector that still needs sixty thousand consent screens.

The deeper point is that the identity rail and the payment rail answer different questions, and EMA makes the split precise. ID-JAG answers may this person's client connect to this server, with these scopes. It says nothing about metering, settlement, or what a call costs. x402 answers who pays for this specific call — with a signed EIP-3009 authorization, no IdP required. The ERC-8004 audit taught us that settlement is the only signal that resists forgery; EMA does not change that, it complements it. The natural composite for an enterprise fleet is identity-gated access with pay-per-use settlement underneath: the IdP decides which employees' agents may reach the gateway, and the gateway's billing layer decides how their usage settles. The sub claim gives us the stable identifier to attribute usage per employee inside a tenant — account linking for billing, following the same sub-primary, email-fallback rule the spec mandates.

There is also a convergence worth naming. The optional authorization_details claim carries RFC 9396 structured grants through the IdP into the access token. That is the same allowance pattern we keep meeting everywhere in the payments stack — ACP's allowance object, ERC-7715 permissions, x402's upto scheme: a principal authorizes a bounded envelope, and infrastructure enforces the bound. An IdP that can say "this user's agent may call these tools with this spend ceiling per day" — and a gateway that enforces it at settle time — is the enterprise version of the spend permission. The pieces now exist on both rails.

Staying on the frontier

Concrete sequence, in dependency order.

First, accept the grant. Add urn:ietf:params:oauth:grant-type:jwt-bearer support to the gateway's authorization server, validate ID-JAGs per the checklist (typ, signature against configured IdP JWKS, exact aud match, client binding, expiry), and advertise urn:ietf:params:oauth:grant-profile:id-jag in authorization_grant_profiles_supported. Pin draft -04 and document the pinned revision, since the IETF text is still moving.

Second, declare the extension on the MCP server and verify the end-to-end flow against the two clients that already ship it — Claude and VS Code — with a test Okta tenant. This is days of work, not months, and it is the difference between appearing and not appearing in an enterprise client matrix.

Third, wire account linking into billing. Map ID-JAG sub to per-employee usage attribution inside tenant accounts, so an enterprise customer gets IdP-governed access and per-user metering from the same integration.

Fourth, track the actor profile. The act/actor_token gap is where per-agent identity will land in this stack. When the OAuth WG or an MCP profile defines normative processing, LLM4Agents should be an early implementer — agent-level identity chained under user-level policy is exactly the primitive a fleet gateway needs, and it slots into the agent-identity work we already do.

Fifth, prototype the composite. An ID-JAG-gated endpoint whose settlement runs on x402 underneath, with authorization_details carrying a spend ceiling the gateway enforces at settle time. Identity decides entry; settlement writes the audit trail. Whoever demonstrates that composite first defines how enterprise agent fleets get billed.

Run your fleet on rails that compose

OpenAI-compatible gateway, IdP-friendly identity, and stablecoin settlement per call.

Register your agent