Token Exchange for MCP Servers: Carrying User Identity Downstream

An MCP server is rarely the system that holds the data. It validates a token, then calls something else: your own product API, a search index, a billing provider, a partner API. The interesting security question is what credential it uses for that second call.

The specification's security guidance is explicit that the answer is not "the token it was given". Passing the incoming access token onward is named as an anti-pattern. We covered why in five findings that fail MCP security reviews. This post is the other half: what to do instead, in enough detail to implement.

The short version. Your server validates the token issued for it, then exchanges that token for a new one, issued for the downstream service, that still names the same user. The mechanism is OAuth 2.0 Token Exchange, RFC 8693.

Why not a service account

The common shortcut is one service credential shared by the whole MCP server, plus a user_id passed to the downstream API as a parameter or header.

This works, and for some architectures it is the only option available today. It has two costs a reviewer will find. First, the downstream system now trusts a header rather than a token, so the security boundary moved into your application code; anyone who can reach the downstream API with that service credential can name any user. Second, the service credential is usually broad, because it must cover every user's every tool. A single leaked credential is a whole-tenant credential.

If you go this route knowingly, make it defensible: the downstream call must carry a signed assertion of the user's identity, not a plain header, and the service credential must be scoped to the operations the MCP server actually performs. Say all of this in your threat model, because you will be asked.

What token exchange does

RFC 8693 defines a grant type on the authorization server's token endpoint. Your MCP server presents the token it received, states which service it needs to call, and gets back a token for that service.

A request looks roughly like this:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <mcp server client credentials>

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<the validated access token from the agent>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.internal.example.com
&scope=invoices.read

The authorization server returns an access token whose sub is still the end user, whose audience is the downstream service, and whose scopes are only what you asked for. Two properties matter:

  • The audience changes. The downstream service can validate the audience strictly and reject anything not issued for it. Audience validation is the check that makes the whole chain meaningful; see validating token audience in MCP servers.
  • The subject does not change. Downstream authorization decisions and downstream audit logs still name the person. This is what "identity propagation" means in practice.

Most enterprise identity providers support the grant. Where clients configure it, the MCP server is registered as a confidential client permitted to exchange tokens for a named set of downstream resources.

The actor claim, and why to set it

Token exchange has an optional actor_token. When you supply your server's own credential as the actor, the issued token carries an act claim: the subject is the user, the actor is the MCP server.

This is delegation stated in the token rather than inferred. It gives the downstream service something it cannot otherwise know: this call came from the agent path, not from the web application. That distinction is worth having. It lets a customer apply a different policy to agent traffic, and it lets an incident responder separate "the user did this" from "an agent acting for the user did this".

Use it. Then use it in your audit events too: subject, actor, tool, tenant, outcome.

Scoping the exchange

A token exchange is an opportunity to narrow, and most implementations waste it.

Request the smallest scope the specific tool needs, not the scope set the agent was granted. If search_invoices needs read access to invoices, exchange for invoices.read and nothing else. Do this per tool, not once per session. The exchanged token then bounds the damage of any downstream bug or injected instruction to what that one tool can do.

Ask for short lifetimes. The exchanged token exists to serve one tool call, or a short burst of them. Minutes, not hours.

And do not request an exchange the tool does not need. A read-only tool that never calls the billing provider should hold no billing token at any point.

Caching without breaking revocation

Exchanging on every call adds a round trip to the authorization server. Caching is reasonable, with rules:

  • Key the cache on subject, downstream resource, and scope set. Never on the tool call alone, and never globally.
  • Store tokens encrypted, in memory or in a secrets store, never in application logs or traces.
  • Expire entries well before the token itself expires, and honour a shorter of the two.
  • Drop the entire cache entry for a subject when the inbound token fails validation, when the session ends, or when a revocation event arrives.

The property you must preserve is that revoking the user upstream stops downstream calls quickly. If your cache holds a one-hour downstream token, then "quickly" means an hour, and you should say so out loud rather than discover it during an incident review.

Failure modes to handle explicitly

Token exchange introduces a dependency on the authorization server in the hot path of every tool call. Design the failures now.

  • Exchange denied. The user genuinely lacks the downstream permission. Return a clean authorization error to the client, emit a denial audit event, and do not fall back to a service credential. A silent fallback destroys the entire model.
  • Authorization server unavailable. Fail closed, with a distinct error the host can surface. Retry with backoff and a hard ceiling; a tool call that hangs for thirty seconds is a worse user experience than a fast, honest failure.
  • Chained exchanges. If a downstream service exchanges again for a third service, cap the depth and keep the actor chain intact. Unbounded delegation chains are hard to reason about and harder to explain in a review.
  • Clock skew and rotation. Downstream services validate signatures and expiry. Cache signing keys with a short TTL and handle rotation without a restart.

What a reviewer will ask

Expect four questions, in roughly this order:

  1. Which credential does the server use for each downstream system, and where does it come from?
  2. Can the downstream system distinguish a call made for a user by an agent from a call made by the user directly?
  3. If we disable an employee in our identity provider, how long until their agent stops being able to act?
  4. Show us an audit event for one tool call, end to end, including the downstream call.

A server built as described above answers all four with artifacts rather than assurances: a data-flow diagram showing where each token is issued and validated, a table of downstream resources with the scopes exchanged for each, and a sample event carrying subject, actor, tool, and correlation ID through both hops.

When you do not need this

If your MCP server holds its own data and calls nothing downstream, there is nothing to exchange. Validate the inbound token, enforce per-user access at the data layer, and stop. Adding token exchange to a single-service architecture buys you complexity and no security.

If your identity provider does not support RFC 8693 today, do not build a private token service to imitate it as a first move. Use a narrowly scoped service credential with a signed user assertion, write down the gap, and revisit when the provider ships the grant. Naming the limitation is a better position in a security review than a bespoke token minter nobody has reviewed.

We design this flow as part of every server build, and downstream credential handling is one of the areas our security audits look at first. If you want a read on how your server carries identity past its own boundary, tell us what you have built.