Enterprise MCP Authorization Architecture: A Multi-Tenant Reference Design
The Model Context Protocol spec finally says something concrete about authorization: use OAuth 2.1, bind every token to a specific audience with resource indicators, and never pass a client token through to a downstream API. The requirements are real. The problem is that they are scattered across the core spec, a security best-practices document, and half a dozen IETF RFCs, and almost every page that ranks for these terms is a security-vendor checklist that stops at the single-server case.
This is the reference design we use when a client needs an MCP server that serves more than one tenant, plugs into an enterprise identity provider, and survives a procurement security review. It is vendor-neutral, it is anchored to the 2025-11-25 spec revision, and it draws the one diagram nobody else draws: the full trust boundary from identity provider to per-tenant data plane. Written from Wavect's AI product and security-hardening work.
Designing an enterprise MCP server?
Talk to WavectWho owns what: the MCP server is a resource server, not an authorization server
The single most common mistake is building the MCP server as its own login system. It is not. Under the current spec the MCP server is an OAuth 2.1 resource server. Its job is to validate tokens, not to issue them. A separate authorization server, which in an enterprise is your identity provider (Entra ID, Okta, Auth0, Keycloak, Ping), interacts with the user and issues access tokens.
This split is recent. The first authorization spec (2025-03-26) expected the MCP server to act as both authorization server and resource server. The 2025-06-18 revision separated the roles through pull request 284, and that separation is what makes enterprise single sign-on possible at all. The authorization server may still be co-located with the resource server, but the mandated responsibility of the MCP server is token validation. Get this role model right and everything downstream falls into place. Get it wrong and you rebuild identity badly.
What does the current spec actually require?
Two caveats before the table. First, authorization in MCP is optional and is defined only for HTTP transports. A server behind a STDIO transport should not follow this spec at all; it pulls credentials from the environment. Second, OAuth 2.1 is still an IETF draft, not a published RFC, so do not cite an RFC number for it. Here is the standards surface for the 2025-11-25 revision.
| Standard | Reference | Role in MCP | Level |
|---|---|---|---|
| OAuth 2.1 | draft-ietf-oauth-v2-1 | Core authorization framework | Auth server MUST |
| Protected Resource Metadata | RFC 9728 | Server points client to its auth server | Resource server MUST |
| Resource Indicators | RFC 8707 | Binds the token to one audience | Client MUST |
| Authorization Server Metadata | RFC 8414 | Auth server discovery | MUST (one of this or OIDC) |
| OpenID Connect Discovery | OIDC Core 1.0 | Alternative auth server discovery | MUST (one of this or 8414) |
| PKCE | OAuth 2.1 Sec 7.5.2 | Protects the authorization code | Client MUST, S256 |
| Client ID Metadata Documents | draft-ietf-oauth-client-id-metadata-document | URL as client_id | SHOULD (new in 2025-11-25) |
| Dynamic Client Registration | RFC 7591 | Automated client registration | MAY (was SHOULD) |
| Bearer token usage | RFC 6750 | Challenge and insufficient_scope | Referenced |
Note the two version deltas that trip people up. The 2025-11-25 revision downgraded Dynamic Client Registration from SHOULD to MAY, and it added a mandatory PKCE S256 method plus a requirement that the client verify PKCE support before proceeding. If the auth server metadata does not advertise code_challenge_methods_supported, a compliant client must refuse to start the flow.
How does the authorization flow work end to end?
The whole handshake is a discovery step followed by a standard authorization code flow. The client hits the server without a token, gets told where to authenticate, authenticates, and comes back with a token minted for this specific server.
Two mandatory details live inside that diagram. On the challenge, the server must return 401 Unauthorized with a WWW-Authenticate header carrying the resource_metadata URL, per RFC 9728. The 2025-11-25 revision also lets the client fall back to probing the well-known URI directly. On the authorization and token requests, the client must send a resource parameter set to the canonical URI of the MCP server, and it must send it whether or not the auth server claims to support it.
Why is token passthrough prohibited, and how do you validate audience?
This is the security heart of the spec, and it is where most home-grown servers fail. The rule is blunt: the MCP server must validate that each access token was issued for it as the intended audience, and it must reject anything else. It must not accept a token minted for another service and it must not forward the token it received to a downstream API. That last anti-pattern is token passthrough, and the spec bans it outright.
The reason is the confused deputy problem. If your server accepts and forwards tokens it did not verify, a token leaked or issued for one service becomes a skeleton key for another, rate limits keyed on audience are bypassed, and the downstream audit trail shows the wrong identity. Audience validation is one claim check, and it is the line between a resource server and a liability.
on every tool call:
token = bearer_from_authorization_header() # never from a session
claims = verify_signature(token, jwks_of(trusted_issuer))
assert claims.iss == expected_issuer # pinned per tenant
assert this_server_uri in claims.aud # RFC 8707 audience
assert not expired(claims) and not before(claims)
assert required_scope_for(tool) in claims.scope
tenant = claims["tenant"] or claims["org_id"]
enforce_tenant(tenant) # every query, every secret
Notice what is not there: no session lookup for authentication. The spec is explicit that servers must verify every inbound request and must not use sessions for authentication. Session identifiers, if you use them at all, must be non-deterministic and bound to user identity so a guessed identifier cannot impersonate another tenant.
How does the server reach downstream APIs without leaking the token?
If the MCP server needs to call a downstream API, it acts as an OAuth client to that API and uses a separate token minted by the upstream authorization server. The spec tells you what not to do (do not pass through the inbound token) but leaves the how to the ecosystem. The dominant pattern is token exchange, defined in RFC 8693: the server swaps the inbound token, whose audience is the MCP server, for a new token whose audience is the downstream API.
The server plays a dual role here: resource server to the client, OAuth client to the API. Prefer delegation over impersonation so the original user stays in the sub claim and the acting chain is recorded in the act claim, which is what keeps your audit trail honest across the hop. Token exchange and on-behalf-of flows sit outside the core MCP spec, so treat them as an identity-provider or gateway responsibility, not an MCP requirement.
What does the multi-tenant reference architecture look like?
Here is the topology. One identity provider issues audience-bound tokens with a realm per tenant. Clients present those tokens to a shared resource server or gateway inside the enterprise trust boundary. The server validates audience, enforces per-tool scope, pins the tenant realm, and only then reaches tools, data, and downstream APIs, each isolated per tenant.
The mistake in most multi-tenant write-ups is treating isolation as a database concern. It is not. Tenancy has to be enforced at every layer, and the tenant identity comes from a verified token claim, never from a tool input the model can influence. The table below is the checklist we walk through per layer.
| Layer | Isolation control | Failure if skipped |
|---|---|---|
| Identity | Pin issuer and realm per tenant; reject tokens from other realms | Tenant A logs in via Tenant B's realm |
| Token | Validate audience (RFC 8707) and expiry on every request | Token minted elsewhere is accepted |
| Scope | Least-privilege scopes mapped from IdP groups and roles | Read role performs a write |
| Tool | Filter visible tools by tenant tier and scope | Agent discovers a tool it cannot use safely |
| Credential | Per-tenant secrets in a vault, opaque to model and client | One tenant's API key serves another |
| Data | Row-level security keyed on the tenant claim | Cross-tenant row read |
| Audit | Per-tenant, immutable logs with correlation IDs | No attribution during an incident |

"The tenant identity is a verified token claim. The moment it comes from a tool argument the model can write, your isolation is theatre."
How do per-tool scopes and step-up authorization work?
The core protocol handles scopes at the OAuth layer, not per individual tool, so true per-tool authorization is something you build on top. The spec gives you the primitives. Start minimal and avoid omnibus scopes like all or full-access. When a tool needs more than the current token grants, the 2025-11-25 revision defines a clean step-up: the server returns 403 with WWW-Authenticate: Bearer error="insufficient_scope" and the required scope, the client runs an incremental authorization for just that scope, and retries.
In practice we map identity-provider groups and SCIM roles to scopes, then gate each tool on a required scope. An analyst role carries invoices:read and sees the read tools; it never carries ledger:write. This is the same discipline we apply in an authorization review, and it is what a procurement team actually tests.
How do you add enterprise SSO to an MCP server?
There are two shapes, and the choice drives most of your operational cost.
The first is OAuth per server: each MCP server points its authorization_servers metadata at the workforce identity provider. Clean for one or two servers, repetitive at fleet scale. The second is an MCP gateway: a single policy-enforced ingress in front of every server that centralises discovery, token validation, scope mapping, token exchange, and audit. For an enterprise running many servers across many tenants, the gateway is usually the right answer because it gives you one place to enforce isolation and one place to audit.
On protocols: OIDC is first-class in the 2025-11-25 revision, which requires the auth server to support RFC 8414 metadata or OpenID Connect Discovery, and requires clients to try both. There is no native SAML in MCP. A SAML-only enterprise bridges through its identity provider or a broker that exposes an OAuth or OIDC front for the MCP server.
How do you govern client registration and credential lifetimes?
Dynamic Client Registration was the soft spot in early deployments because it let arbitrary clients enrol. The 2025-11-25 revision demotes it to MAY and prefers a three-tier model: pre-registered credentials for known clients, then Client ID Metadata Documents where the client uses an HTTPS URL as its client_id, then DCR as a fallback. If you allow either of the latter two, allowlist issuers and watch for the SSRF and localhost-redirect risks the spec calls out. Enterprises should tie registration to a tenant and put unknown clients through an admin-review queue.
Credential lifetimes follow OAuth 2.1: issue short-lived access tokens, rotate refresh tokens for public clients, store tokens securely, keep tokens out of URL query strings, and send the bearer token on every request. Concrete defaults we use as a starting point:
| Credential | Typical lifetime | Rotation |
|---|---|---|
| Access token (client to server) | 5 to 15 minutes | Re-mint from refresh token |
| Refresh token (public client) | Hours to days, sliding | Rotate on every use |
| Downstream token (RFC 8693) | Minutes, per call or per session | Re-exchange, never cache broadly |
| Per-tenant secret in vault | Long, but revocable | Scheduled plus on suspected leak |
What audit events must you capture?
The core spec has no dedicated audit-logging requirement, but the token-passthrough analysis makes the case for you: passthrough breaks the audit trail because downstream logs show the wrong identity. In a multi-tenant system, audit is how you prove isolation held. Log these, per tenant, with a correlation ID that survives the downstream hop.
- Token validation outcomes: accepted, rejected on audience, rejected on issuer, expired.
- Authorization decisions: tool invoked, scope required, scope granted, allow or deny.
- Step-up events: scope requested and the subset granted.
- Token exchange: which downstream audience was minted, for which subject and actor.
- Tenant context: the verified tenant claim on every entry, so a query is attributable to a tenant, a user, and a tool.
Per server, gateway, or managed identity: which do you pick?
A quick decision matrix for the three viable topologies.
| Approach | Best when | Cost |
|---|---|---|
| OAuth per server | One or two servers, one tenant | Low setup, poor at scale |
| MCP gateway | Many servers or many tenants | Higher setup, one enforcement point |
| Managed IdP integration | You already live in Entra or Okta | Vendor lock, fastest compliance |
Going from a single-tenant server to a multi-tenant one is not a rewrite if you sequence it: introduce the tenant claim and pin the issuer first, move secrets into a per-tenant vault, add row-level security keyed on the claim, then filter tool visibility and turn on per-tenant audit. Each step is shippable and testable on its own. Test isolation adversarially, replay Tenant A's token against Tenant B's data and confirm it is rejected at the token layer, not merely filtered at the query layer.
Final thoughts
The MCP authorization spec gives you three hard requirements: OAuth 2.1 as the frame, audience-bound tokens through resource indicators, and no token passthrough. Everything else that matters for an enterprise, multi-tenant isolation, per-tool scopes, delegated downstream access, and audit, lives above the spec and is yours to design. Treat the MCP server as a resource server that validates and enforces, keep tenant identity in a verified claim, and log enough to prove isolation held. Do that and an MCP server stops being the weakest link in your agent stack.
Sources and further reading
- Model Context Protocol, Authorization specification (2025-11-25 revision)
- Model Context Protocol, Authorization specification (2025-06-18 revision)
- Model Context Protocol, Security Best Practices (token passthrough, confused deputy)
- MCP pull request 284, separating the resource server and authorization server roles
- RFC 9728, OAuth 2.0 Protected Resource Metadata
- RFC 8707, Resource Indicators for OAuth 2.0
- RFC 8414, OAuth 2.0 Authorization Server Metadata
- RFC 7591, OAuth 2.0 Dynamic Client Registration
- RFC 8693, OAuth 2.0 Token Exchange
- RFC 9068, JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens
- RFC 6750, OAuth 2.0 Bearer Token Usage
- OAuth 2.1 Authorization Framework (IETF draft)