In this piece
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 was rechecked on 2 September 2026 against the 2026-07-28 spec revision, and it draws the full trust boundary from identity provider to per-tenant data plane. Written from Wavect's AI product and security-hardening work.
This page implements the MCP authorization layer. If you are still deciding whether MCP itself can be the boundary, start with why MCP is not a security boundary and how data-level access control closes the gap.
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, which lets enterprise identity providers fit the architecture cleanly. 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 for HTTP-based transports. A server using STDIO should not follow this authorization specification; it retrieves 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 2026-07-28 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 | OpenID Connect Discovery 1.0 | Alternative auth server discovery | MUST (one of this or 8414) |
| Authorization Server Issuer Identification | RFC 9207 | Prevents authorization-server mix-up | Server SHOULD send, client MUST validate when present |
| PKCE | OAuth 2.1 Sec 7.5.2 | Protects the authorization code | Client MUST implement and verify support |
| Client ID Metadata Documents | draft-ietf-oauth-client-id-metadata-document-00 | HTTPS URL as client_id | Client and auth server SHOULD |
| Dynamic Client Registration | RFC 7591 | Backward-compatible client registration | Deprecated, MAY |
| Bearer token usage | RFC 6750 | Challenge and insufficient_scope | Referenced |
The 2026-07-28 revision formally deprecates Dynamic Client Registration in favor of Client ID Metadata Documents, binds stored client credentials to the authorization server issuer that created them, and adds RFC 9207 authorization-response issuer validation. PKCE remains mandatory: clients must verify code_challenge_methods_supported before proceeding and use S256 when technically capable.
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. The server must expose RFC 9728 Protected Resource Metadata through a WWW-Authenticate header on a 401 Unauthorized response or at a well-known URI. Clients must support both mechanisms, use the header URL when present, and otherwise probe the endpoint-specific and root well-known URIs in that order. On authorization and token requests, the client must send a resource parameter set to the canonical URI of the MCP server, whether or not the authorization server advertises support.
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() # every request is self-contained
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
The 2026-07-28 core removed the protocol handshake and Mcp-Session-Id. Each request is self-contained, and the authorization specification requires the bearer token on every HTTP request. The pseudocode assumes JWT access tokens because this reference design uses verified claims; MCP does not mandate JWTs. With opaque tokens, obtain equivalent verified issuer, audience, scope, and tenant context through introspection or a trusted gateway.
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 for that API. The spec tells you what not to do (do not pass through the inbound token) but leaves the how to the ecosystem. One standardized option is token exchange, defined in RFC 8693: the server exchanges 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 server should return 403 with WWW-Authenticate: Bearer error="insufficient_scope", all scopes required for the current operation, and the resource_metadata URI. A user-facing client should request the union of those scopes and its previously requested scopes, then retry only a limited number of times.
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: the 2026-07-28 revision requires an authorization server to provide RFC 8414 metadata or OpenID Connect Discovery, and clients must support both discovery mechanisms. Protected Resource Metadata may list multiple authorization servers; the client selects one and must keep credentials and tokens separate by issuer. There is no native SAML flow in MCP. A SAML-only enterprise bridges through an identity provider or broker that exposes OAuth or OIDC for the MCP server.
How do you govern client registration and credential lifetimes?
The 2026-07-28 revision formally deprecates Dynamic Client Registration. Clients that support all options should prefer pre-registered credentials, then Client ID Metadata Documents when the authorization server advertises support, then DCR only as a backward-compatible fallback, and finally user-entered client information. Persisted pre-registered or DCR credentials must be bound to the issuer that created them and must not be reused with another authorization server. Authorization servers fetching Client ID Metadata Documents should mitigate SSRF, validate exact redirect URIs, and warn clearly about localhost-only redirects. Enterprises should tie registration to a tenant and put unknown clients through an admin-review queue.
The current MCP specification says authorization servers should issue short-lived access tokens, must rotate refresh tokens for public clients, and may decline to issue refresh tokens at all. Clients must protect refresh tokens in transit and storage, keep access tokens out of URL query strings, and send the bearer token on every HTTP 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 foundational requirements for this architecture: 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 (2026-07-28 revision)
- Model Context Protocol, Authorization server discovery
- Model Context Protocol, Client registration
- Model Context Protocol, Authorization security considerations
- Model Context Protocol, 2026-07-28 specification release
- Model Context Protocol, Authorization specification (2025-06-18 revision)
- 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 9207, OAuth 2.0 Authorization Server Issuer Identification
- RFC 7591, OAuth 2.0 Dynamic Client Registration
- OAuth Client ID Metadata Document (IETF draft)
- OpenID Connect Discovery 1.0
- 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)