Skip to main content

Your MCP Server Doesn't Know Who's Deploying. WorkOS's OAuth 2.1 Stack Fixes That

10 min readDora NodaDora Noda
Share
On this page

Only about 8.5% of MCP servers in production use OAuth today. The rest — the majority, by a wide margin — sit behind a static API key or personal access token pasted into an environment variable. That's a workable trust model for a read-only "search my docs" tool. It is not a workable trust model for an MCP server that can deploy, roll back, and read logs on a fleet of production machines, because a bearer token can answer exactly one question — "was this the right secret?" — and a deploy-authority tool call needs a harder one answered first: which agent, acting on whose behalf and under what scope, just asked to ship code to production?

In May 2026, WorkOS's AuthKit shipped resource indicators and Client ID Metadata Document (CIMD) support for MCP auth, completing its run at the full stack the Model Context Protocol's authorization spec actually calls for: OAuth 2.1, Dynamic Client Registration, Protected Resource Metadata, Resource Indicators, and CIMD. That's not a minor SDK bump. It's the difference between an MCP server that trusts whoever holds a string and one that can answer "which agent, which scope, which human authorized it" for every tool call in its audit log — the exact question a deploy-authority MCP server has to answer before anyone should hand it real production access.

The Bearer-Token Problem, In One Log Line

Here's what a typical infra MCP server's auth looks like today: a DEPLOY_TOKEN environment variable, checked against an Authorization: Bearer header, full stop. It works. It's also why an audit log for a deploy tool ends up looking like this:

text
2026-07-29T14:02:11Z  token=sk_live_...a91f  action=deploy  service=svc-web  result=ok

That line answers "was the secret valid." It cannot answer any of the questions that actually matter after something goes wrong:

  • Which agent made this call? A dozen tools, scripts, and CI jobs might share the same token. The log can't distinguish a scheduled rollback bot from an AI agent that decided on its own to redeploy.
  • On whose authority? Was a human in the loop, or did an agent act autonomously? The token doesn't carry that distinction — it's the same string either way.
  • Under what scope? A token minted for "read logs" and one minted for "deploy and roll back" often live in the same env-var-shaped bucket, because scoping bearer tokens finely enough to matter is exactly the kind of infrastructure most teams skip.
  • How do you revoke just this caller? Rotating the token kills every integration sharing it, not just the compromised one.

None of this is a hypothetical footgun. Security write-ups on MCP auth in 2026 keep converging on the same three failure modes: over-scoped tokens, prompt-injection-driven credential exfiltration, and missing server-side attestation — all three symptoms of the same root cause, which is that a bearer token is an authentication mechanism wearing an authorization mechanism's job.

What Actually Shipped: DCR, PRM, Resource Indicators, and CIMD

The MCP authorization spec (current as of the 2025-11-25 revision) doesn't leave this to vendor discretion — it names four specific mechanisms, and WorkOS's May 2026 GA is notable because it's the first widely-used identity platform to ship all four together rather than one or two:

Protected Resource Metadata (RFC 9728) is how a client finds out how to authenticate with a given MCP server in the first place. The server exposes a /.well-known/oauth-protected-resource endpoint that returns its resource identifier and which authorization servers it trusts — no more hardcoding "use this token" in a README.

Dynamic Client Registration (RFC 7591) lets a client register itself with an authorization server on the fly, since an MCP client can't realistically pre-register with every server a user might point it at. WorkOS still supports it, but only "for backwards compatibility with MCP clients that don't yet support Client ID Metadata Document" — DCR is now the legacy path, not the default.

Client ID Metadata Documents (CIMD), added to the MCP spec in November 2025, are the default WorkOS actually recommends. Instead of a client registering ahead of time, its client_id is a URL pointing to a JSON document describing the client. The authorization server fetches that URL, reads the client's declared name, redirect URIs, and metadata, and decides whether to trust it — no pre-registration step, no manual approval queue, and a server can distinguish "official Bex CLI, published at cli.bex.co/.well-known/client-metadata.json" from "some agent nobody registered" just by dereferencing a URL.

Resource Indicators (RFC 8707) are what stop a token issued for one MCP server from being usable against another. A client requesting a token has to name the specific resource — the MCP server's own URL — in the resource parameter, both at authorization time and at token-request time. The authorization server bakes that value into the token's aud (audience) claim. A deploy MCP server that validates aud on every request will reject a token minted for a different server, even if that token is otherwise valid and unexpired.

Here's what the four mechanisms look like chained together for a real deploy call:

  1. An agent's MCP client hits deploy-mcp.example.com/.well-known/oauth-protected-resource and learns which authorization server to talk to (PRM).
  2. It presents its client_id — a URL like https://myagent.example.com/.well-known/client-metadata.json — which the authorization server fetches to learn who's asking (CIMD, no pre-registration needed).
  3. It requests a token with resource=https://deploy-mcp.example.com, so the issued token's aud claim is scoped to that one server and no other (Resource Indicators).
  4. The deploy MCP server validates that aud claim before executing the tool call, rejecting any token minted for a different resource.

That chain answers "was this client legitimate" and "is this token even usable against this specific server." It still hasn't answered the harder half of the original question — which specific agent, acting for which human, made the call. That's a separate mechanism, and it's the one most write-ups on this stack skip past.

CIMD identifies the client application — "this request came from the official Bex deploy agent," not "this specific agent instance, acting for this specific person, right now." Resource Indicators scope the token to the right server. Neither one, on its own, closes the loop the bearer-token log line couldn't answer: which agent, on whose authority.

The piece that does is RFC 8693 token exchange — what WorkOS's changelog describes as "on-behalf-of token exchange for AI agents," shipped alongside CIMD and resource indicators in the same GA push. Token exchange lets an authorization server mint a new token that preserves the original user's sub (subject) claim while adding a separate act (actor) claim identifying the agent now acting on that user's behalf. The two claims stay distinct on purpose: sub says whose authority this is, act says who's actually driving. If that agent hands off to a second agent, the act claim nests, so a chain of delegation is reconstructable from one token instead of guessed at from correlated timestamps across three log files.

Put all three mechanisms into one token and the deploy audit log stops looking like a bare hash and starts looking like this:

text
2026-07-29T14:02:11Z  sub=tian.pan@aya.yale.edu  act=bex-deploy-agent-v3  \
  aud=deploy-mcp.bex.internal  action=deploy  service=svc-web  result=ok

Every question the bearer-token line couldn't answer is now a field: sub is the human who authorized the session, act is the specific agent that made the call, aud confirms the token was only ever valid against this one deploy server. Revoking access to just that agent means revoking its actor grant, not rotating a shared secret that also breaks the CI pipeline and the on-call bot. That's the concrete gap between "a valid bearer token deployed something" and "we know who did it and under whose authority" — and it's the reason the four RFCs the MCP spec names aren't bureaucratic box-checking. Without the act claim specifically, CIMD and resource indicators still leave the actual question — which agent — unanswered.

Why Resource Indicators Are the Line That Stops a Confused Deputy

There's a second reason resource indicators matter beyond routing, and it's worth spelling out because it's the attack a deploy-authority MCP server is most exposed to: the confused deputy problem.

Say an agent legitimately holds a token scoped to a read-only "logs" MCP server — narrow, low-risk, exactly the kind of access a monitoring assistant should have. Now say that same agent, or a prompt-injected version of it, tries to present that token to a separate "deploy" MCP server instead. If the deploy server doesn't check the token's audience — just verifies the signature and expiry, the way a lot of hand-rolled bearer-token checks do — it will happily accept a token that was never meant for it, and the "logs" agent has just talked its way into deploy authority it was never granted.

This is exactly the token-passthrough anti-pattern the MCP spec calls out explicitly as prohibited: an MCP server accepting and forwarding a token without validating it was minted for that specific server. Resource indicators are the mechanism that makes the check possible in the first place — without a resource parameter at token-request time, there's no reliable aud claim to check, and "verify the audience" is advice with nothing to verify. With it, a deploy MCP server's entire audience-validation logic is one comparison: does this token's aud claim equal my own server identity? If not, reject, regardless of whether the token is otherwise perfectly valid.

What This Actually Costs to Build vs. What You Can Now Buy

None of this is free, but the ledger has shifted meaningfully as of May 2026. What used to require rolling your own OAuth 2.1 authorization server, DCR endpoint, PRM discovery document, and token-exchange logic — a multi-week project most infra teams reasonably skipped in favor of a bearer token — is now something an identity provider like WorkOS, Auth0, or a handful of others will issue off the shelf. Concretely, here's what a platform can now get versus what it still has to own itself:

Now buyable:

  • OAuth 2.1 authorization server behavior (PKCE, proper grant flows)
  • PRM discovery (/.well-known/oauth-protected-resource)
  • CIMD-based client identification, no pre-registration queue to maintain
  • Resource-indicator-scoped tokens with a real aud claim
  • act-claim token exchange for on-behalf-of agent delegation

Still yours to build:

  • Per-tool scope definitions — deciding that deploy and rollback are different scopes from read-logs, and that a scope granted for one service doesn't imply another
  • The audit trail mapping that actually surfaces sub/act/aud in a readable deploy log, rather than leaving those claims buried in a JWT nobody reads until an incident forces the question
  • A revocation and consent policy for what happens when an agent's actor grant needs to be pulled mid-deploy, not just at token expiry

For a platform like Bex — a Render-compatible API where AI agents are meant to be first-class operators, not an afterthought bolted onto a human dashboard — that buy-side list is exactly the layer a deploy/rollback/logs MCP server needs before it's handed real production authority. The bearer-token-in-an-env-var approach was never going to survive contact with "an agent I don't fully control has the keys to my fleet." The build side — actually deciding what a deploy scope means, and making sure the audit trail reads like the log line above instead of a bare token hash — is the part no identity vendor can do for you, because it's a statement about what your own platform is willing to let an agent do unsupervised.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with agents built in as first-class operators from day one. Star the repo on GitHub or deploy your first app today.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide