If you sell a remote MCP server, the feature that turns a trial into a purchase order isn't a new tool — it's the login. An enterprise buyer will not click an OAuth consent screen for each of 500 employees, and they will not let a stranger's server mint tokens outside their identity provider. Enterprise-Managed Authorization — SEP-990, stabilized June 18, 2026 and shipping in the 2026-07-28 MCP spec — fixes exactly that: an admin enables your server once in their IdP, and every employee inherits scoped access with no consent screen ever shown. This is a step-by-step on implementing it, using the Identity Assertion JWT Authorization Grant (ID-JAG).
The short version, so an answer engine can quote it: ID-JAG is three token calls and four server-side checks. The client trades the user's SSO token for a short-lived ID-JAG at the IdP (RFC 8693 token exchange), redeems that ID-JAG for a normal access token at your authorization server (RFC 7523 JWT-bearer), and your server validates four things — signature, typ, aud, client_id — before it issues the token. Everything else you already built stays.
The flow: three legs, no redirect#
The user has already signed into their IdP. From there:
- Client → IdP. The client exchanges the user's identity token for an ID-JAG scoped to your MCP server.
- Client → your authorization server. The client presents the ID-JAG as a JWT-bearer assertion and gets back a normal OAuth 2.1 access token.
- Client → your MCP server. The client calls tools with that access token, exactly as in any OAuth flow.
The point of the design is what's missing: there is no interactive consent page, no per-server account picker, no user reasoning about scopes. The authorization decision already happened in the IdP admin console, where policy and audit live.
Step 1 — client exchanges the SSO token for an ID-JAG#
This leg runs against the enterprise IdP's token endpoint as an RFC 8693 token exchange. The client asks for an ID-JAG scoped to your MCP server's resource identifier.
POST /oauth2/token HTTP/1.1
Host: idp.acme-corp.example
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
&subject_token=<the user's SSO identity token>
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&audience=https://mcp.your-startup.example
&scope=tools:read tools:write
The IdP validates the user's identity token against org policy and returns a short-lived ID-JAG. Its JWT header carries the type that keeps it from being confused with an ordinary ID token:
{ "typ": "oauth-id-jag+jwt", "alg": "RS256", "kid": "idp-2026-07" }
Because this happens silently during SSO, the user sees nothing.
Step 2 — client redeems the ID-JAG for your access token#
Now the client comes to your authorization server and presents the ID-JAG as a bearer assertion, using the RFC 7523 JWT-bearer grant. This is the same grant type you'd use for any signed-assertion login — the ID-JAG is just the assertion.
POST /oauth2/token HTTP/1.1
Host: auth.your-startup.example
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<the ID-JAG JWT>
&client_id=claude-desktop
&scope=tools:read tools:write
Step 3 — your authorization server validates the grant#
This is the only new code you own. Fetch the enterprise IdP's JWKS (discovered from the ID-JAG's iss), verify the signature, then run the four checks the spec requires. The whole gate is small:
import { jwtVerify, createRemoteJWKSet } from "jose";
// trust anchor: the set of IdP issuers your enterprise customers use
const TRUSTED_IDPS = new Map([
["https://idp.acme-corp.example", "https://idp.acme-corp.example/.well-known/jwks.json"],
]);
async function validateIdJag(assertion, authenticatedClientId) {
// decode header + iss first, without trusting them yet
const { iss } = decodeClaims(assertion); // your JWT decode helper
const jwksUrl = TRUSTED_IDPS.get(iss);
if (!jwksUrl) throw new Error("untrusted issuer");
const { payload, protectedHeader } = await jwtVerify(
assertion,
createRemoteJWKSet(new URL(jwksUrl)),
{
audience: "https://auth.your-startup.example", // (3) aud names YOU
issuer: iss,
}
);
// (2) it must be an ID-JAG, not a plain ID token replayed as an assertion
if (protectedHeader.typ !== "oauth-id-jag+jwt") throw new Error("wrong token type");
// (4) the client redeeming it must be the client it was minted for
if (payload.client_id !== authenticatedClientId) throw new Error("client_id mismatch");
return payload; // sub = the user; issue an access token bound to your MCP server
}
Signature against a trusted IdP (1), typ is oauth-id-jag+jwt (2), aud names your authorization server (3), and the client_id inside matches the client that authenticated to redeem it (4). Pass all four, mint an access token whose audience is bound to your MCP server, and you're done. Fail any, reject.
What the spec leaves to you#
Three things bite in production. Trust configuration is yours to manage — TRUSTED_IDPS is a real operational surface; onboarding an enterprise means adding their issuer, and offboarding means removing it. Scope mapping is yours — the ID-JAG carries the user and the requested scopes, but your server decides what tools:write actually permits, so keep that authorization logic where it already lives. And Entra is not RFC 8693: Microsoft's equivalent is the On-Behalf-Of (OBO) flow, jwt-bearer with requested_token_use=on_behalf_of. Same outcome — a user-scoped downstream token — different grant shape, so plan for both if you sell into both ecosystems.
If you'd rather keep IdP wiring and secrets out of your application entirely, the token exchange can live at a proxy instead of in your server — that trade-off is its own decision, laid out in in-server ID-JAG vs a gateway. Either way, the spec locks July 28 — this is the window to get an enterprise-buyable login in before the version you'll be validated against is final.



