engineering2026-03-278 min read

Why Emcy Doesn't Do Token Passthrough — and Why That Matters

A deep dive into Emcy's MCP OAuth architecture: two-token isolation, spec-compliant discovery, and why the MCP protocol explicitly forbids forwarding client tokens to upstream APIs.

E

Emcy Team

Engineering

Diagram showing Emcy's two-token MCP OAuth architecture with proper trust boundaries

When we set out to build Emcy Gateway, we had one non-negotiable constraint: follow the protocol exactly. Not "inspired by." Not "compatible with." The actual spec, as written.

That decision shaped every part of our architecture — and the result is fundamentally different from what most people assume when they hear "OAuth for MCP."

This post explains what we built, why, and what you need to know if you're evaluating how to securely connect your OAuth API to AI agents.

The problem: AI agents need to call your API as your user

The Model Context Protocol gives AI clients (Claude, ChatGPT, VS Code, Cursor) a standard way to discover and call tools exposed by remote servers. But if those tools talk to your API, the MCP server needs credentials — specifically, an OAuth token scoped to the user who's making the request.

The naive approach is token passthrough: the MCP client gets a token from your auth server, sends it to the MCP server, and the MCP server forwards it to your API.

The MCP spec explicitly forbids this.

Why token passthrough is forbidden

The MCP authorization specification is unambiguous:

The MCP server MUST NOT forward the access token it received from the MCP client to upstream services.

This isn't a suggestion. It's a security requirement, and for good reason:

  1. Broken trust boundaries. If the MCP client's token is the same token your API sees, then the MCP server is just a proxy. Any compromise of the MCP server leaks credentials with full API access.

  2. No accountability. Your API can't distinguish between "user called my API directly" and "an AI agent called my API through an MCP server." Your audit trail becomes meaningless.

  3. Scope confusion. The MCP client requested a token with certain scopes for the MCP server. If that same token hits your API, the scopes may not match what your API expects. You've lost fine-grained access control.

  4. Revocation gaps. Revoking the MCP client's access should not require revoking the user's API access. With passthrough, they're the same token — you can't revoke one without the other.

How Emcy actually works: two tokens, two trust boundaries

Emcy uses a two-token architecture where the MCP client's token and your API's token are completely separate:

Token A: MCP Client → Emcy

When a user connects an MCP client (say, Claude Desktop) to your Emcy Gateway-backed MCP server, the client goes through a standard OAuth 2.1 flow:

  1. The client discovers Emcy's authorization server via /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server.
  2. The client initiates authorization with PKCE and the resource parameter targeting the MCP server.
  3. Emcy's authorization server authenticates the user (by redirecting to your auth server — more on this below).
  4. Emcy issues Token A — an access token scoped to the MCP server, with the correct audience claim.

Token A is what the MCP client uses for every subsequent tool call. It was issued by Emcy, for Emcy, and Emcy validates it on every request (audience, issuer, expiry, signature).

Token B: Emcy → Your API

During the authorization flow, Emcy also obtains a separate token from your authorization server. This is a standard OAuth grant where Emcy is the client and your auth server is the authorization server.

  • Emcy stores Token B server-side, encrypted, tied to the user identity.
  • Token B is never sent to the MCP client. The MCP client doesn't know it exists.
  • When a tool call comes in, Emcy validates Token A, looks up the stored Token B, and calls your API with Token B.

Your API sees a normal authenticated request. It can enforce RBAC, rate limits, audit logging, and scoping — exactly as it would for any other client.

The boundary in practice

MCP Client (Claude)          Emcy MCP Server              Your API
      │                            │                          │
      │── tool_call + Token A ────▶│                          │
      │                            │── validate Token A       │
      │                            │── load stored Token B    │
      │                            │── API request + Token B ▶│
      │                            │                          │── authenticate
      │                            │                          │── authorize (RBAC)
      │                            │◀── response ─────────────│
      │◀── tool result ───────────│                          │

Token A and Token B never mix. The MCP client can't extract Token B. Your API never sees Token A. Each token lives in its own trust boundary.

What Emcy publishes

For the protocol to work, both the MCP server and the authorization server need to publish discovery documents. Here's exactly what Emcy exposes for each Gateway-backed MCP server:

Protected Resource Metadata

GET /.well-known/oauth-protected-resource

Returns a JSON document with at least one authorization_servers entry pointing to Emcy's AS. This is how MCP clients discover where to get tokens.

Authorization Server Metadata

GET /.well-known/oauth-authorization-server

Standard RFC 8414 metadata including the authorization endpoint, token endpoint, supported grant types, PKCE requirement, and supported scopes.

WWW-Authenticate on 401

When an MCP client sends a request without a valid token, Emcy returns:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://your-app.emcy.dev/.well-known/oauth-protected-resource"

This tells the client exactly where to find the metadata needed to begin the authorization flow.

MCP Transport

POST /mcp  (Streamable HTTP with SSE)

The actual MCP transport endpoint where tool calls, resource reads, and prompt invocations happen — all authenticated via Token A.

The consent problem (and how we solve it)

There's an architectural nuance that matters for production: Emcy uses a static upstream client ID to talk to your auth server. That means many different MCP clients (Claude, ChatGPT, Cursor) all end up using the same Emcy client registration with your AS.

The MCP spec has clear requirements here. Emcy must:

  • Implement per-client consent: Store consent per (user, client_id) pair so users explicitly approve each MCP client.
  • Display scopes and redirect URI: Show the user exactly what the MCP client is requesting and where callbacks go.
  • Validate exact redirect URIs: No wildcards, no prefixes. Exact match only.
  • CSRF/state protections: Prevent authorization code injection.
  • Prevent clickjacking: X-Frame-Options or CSP frame-ancestors on all authorization UI.

We implement all of these. When a user connects Claude Desktop to your Emcy Gateway-backed server for the first time, they see a consent screen showing exactly what scopes are requested, for which MCP client, and they can approve or deny.

The compliance checklist

If you're building or evaluating MCP OAuth infrastructure, here's the minimum bar for spec compliance:

RequirementWhat it means
OAuth 2.1 security measuresHTTPS everywhere, PKCE required, no implicit grant
Protected Resource Metadata/.well-known/oauth-protected-resource with authorization_servers
401 with WWW-AuthenticatePoints to resource metadata URL
Authorization Server Metadata/.well-known/oauth-authorization-server per RFC 8414
PKCE + resource parameterMCP clients must use both
Token audience validationReject tokens not issued for this server
No token passthroughServer-side downstream grants, separate from MCP tokens
Per-client consentIf using static upstream client ID with multiple MCP clients

Emcy implements every item on this list. It's not optional — it's what "following the protocol" means.

Why this architecture matters for you

If you're an API provider considering how to connect your API to AI agents, the architecture choice has real consequences:

With passthrough (non-compliant):

  • Your API tokens are exposed to every MCP client
  • You can't distinguish human usage from AI agent usage in audit logs
  • Revoking AI agent access means revoking the user's API token
  • A compromised MCP server leaks your users' API credentials

With Emcy's two-token model (spec-compliant):

  • Your API tokens never leave Emcy's server-side storage
  • Your audit trail shows the real user identity on every request
  • You can revoke MCP access without touching API access
  • A compromised MCP client can't access your API directly

Getting started

Emcy makes this entire architecture invisible to you. Bring your own runtime, generate one from OpenAPI, or run it on Emcy Host. Gateway then implements the public edge: discovery, consent, token isolation, and transport.

Any MCP client can connect by adding a single URL:

claude mcp add my-app https://my-app.emcy.dev/mcp

The client discovers the OAuth metadata, authenticates the user through your auth server, gets an Emcy-issued MCP token, and starts calling tools. Your API sees authenticated requests. Your audit logs work. Your RBAC works. Nothing changes on your side.

Start your free trial →


Have questions about MCP OAuth architecture? Reach out at engineering@emcy.ai or open an issue on our GitHub.

Tags
MCP
OAuth
security
architecture
RFC