ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop on the web portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
Development
Policies
    Policy Catalog
    Authentication
    Authorization
    MCP Authorization
      MCP OAuthMCP Auth0 OAuthMCP Clerk OAuthMCP Amazon Cognito OAuthMCP Microsoft Entra OAuthMCP Google OAuthMCP Keycloak OAuthMCP Logto OAuthMCP Okta OAuthMCP OneLogin OAuthMCP Ping OAuthMCP WorkOS OAuthMCP Token Exchange
    Security & Validation
    Metrics, Billing & Quotas
    Testing
    Request Modification
    Response Modification
    Upstream Authentication
    Archival
    GraphQL
    Other
    Guides
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
MCP Authorization

MCP Token Exchange Policy

MCP Gateway Policy

This policy is for use with the MCP Gateway. See the MCP Gateway documentation to learn how to proxy and secure MCP servers with Zuplo.

Resolve gateway-managed upstream MCP credentials and apply them to the request.

Use this after gateway auth when the upstream requires Zuplo-managed OAuth. Omit it for public upstreams or upstreams handled by ordinary Zuplo header/API-key policies. The route should use McpProxyHandler with the upstream URL configured on the handler.

Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

config/policies.json
{ "name": "my-mcp-token-exchange-inbound-policy", "policyType": "mcp-token-exchange-inbound", "handler": { "export": "McpTokenExchangeInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "displayName": "Linear", "id": "linear", "scopes": [], "summary": "Native Linear remote MCP server." } } }

Policy Configuration

  • name <string> - The name of your policy instance. This is used as a reference in your routes.
  • policyType <string> - The identifier of the policy. This is used by the Zuplo UI. Value should be mcp-token-exchange-inbound.
  • handler.export <string> - The name of the exported type. Value should be McpTokenExchangeInboundPolicy.
  • handler.module <string> - The module containing the policy. Value should be $import(@zuplo/runtime).
  • handler.options <object> - The options for this policy. See Policy Options below.

Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

  • id <string> - Stable id for the upstream connection. Used to namespace per-user OAuth state and audit events. If omitted, the gateway tries to infer it from the policy name (mcp-token-exchange-{id}).
  • displayName (required) <string> - Display name shown in connect-required responses, audit logs, and the setup UI.
  • summary <string> - Optional human-readable summary of the upstream, shown on the consent page.
  • protectedResourceMetadataUrl <string> - Optional override for the upstream's OAuth protected-resource metadata URL. Defaults from the route handler's rewritePattern.
  • authMode (required) <string> - Authentication mode. user-oauth performs per-user OAuth federation; shared-oauth uses a gateway-wide OAuth grant. Allowed values are user-oauth, shared-oauth.
  • scopes <string[]> - OAuth scopes to request from the upstream (for OAuth modes).
  • scopeDelimiter <string> - Delimiter used to join scopes in the OAuth authorization request. Defaults to a single space.
  • clientRegistration <undefined> - OAuth client registration mode. Defaults to auto, which uses Client ID Metadata Documents when the upstream advertises support and falls back to Dynamic Client Registration otherwise.

Using the Policy

Overview

The mcp-token-exchange-inbound policy resolves gateway-managed upstream MCP credentials and applies them to the request before the normal Zuplo route handler forwards it.

Use this policy only when Zuplo manages upstream OAuth credentials, such as per-user OAuth or shared OAuth. If the upstream is public, uses an API key header, or only needs static routing/context headers, omit this policy and compose the existing Zuplo header/auth policies instead.

Zuplo is only the gateway. It discovers the upstream MCP server, sends users through the upstream OAuth flow, stores the resulting upstream connection, and adds the upstream credential before forwarding tool traffic. It does not invent provider scopes, register provider apps on your behalf when the provider blocks registration, or hide provider setup failures behind a generic gateway error.

The policy does not perform the normal upstream fetch and does not pass hidden context to the handler. The route should use McpProxyHandler with a deterministic upstream MCP URL configured on the handler. McpProxyHandler handles GET stream probes and delegates POST forwarding to Zuplo's urlRewriteHandler. The policy only installs a response hook for MCP OAuth retry/connect-required cases.

Projects using this policy must run with a compatibility date that enables chained response hooks, currently 2026-03-01 or later. The retry hook must receive the latest response in the policy chain so later response hooks cannot accidentally replace an upstream OAuth retry or connect-required response.

Configuration

Code
{ "name": "mcp-token-exchange-linear", "policyType": "mcp-token-exchange-inbound", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpTokenExchangeInboundPolicy", "options": { "displayName": "Linear", "authMode": "user-oauth", "scopes": [], "clientRegistration": { "mode": "auto" } } } }

The upstream MCP server URL comes from the route handler's rewritePattern, the same place McpProxyHandler uses when forwarding traffic.

Scope Selection

Set scopes when the upstream provider requires specific OAuth scopes that are not discoverable from the MCP challenge or protected resource metadata. Some providers reject an otherwise valid authorization request when scope is empty or incomplete.

When scopes is omitted or empty, the gateway uses the first scope source it can discover:

  1. The upstream WWW-Authenticate challenge scope value.
  2. The upstream protected resource metadata scopes_supported value.
  3. No scope parameter if the upstream does not advertise one.

Explicit configured scopes always win. Use them for providers such as Microsoft 365 where the correct resource-specific application scope is known from the provider configuration rather than from MCP discovery.

Route Shape

Publish both MCP transport methods as one Zuplo multi-method operation using get,post. POST is the stateless Streamable HTTP route that forwards to the upstream. GET uses the same route and returns 405 Method Not Allowed with Allow: POST from McpProxyHandler before upstream dispatch.

Code
{ "/mcp/linear": { "get,post": { "operationId": "linearMcp", "x-zuplo-route": { "policies": { "inbound": ["auth0-managed-oauth", "mcp-token-exchange-linear"] }, "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpProxyHandler", "options": { "rewritePattern": "https://mcp.linear.app/mcp" } } } } } }

Troubleshooting Upstream OAuth

Gateway authorization errors fall into three buckets:

BucketWhat it meansWhat to fix
Gateway configurationThe route or policy options are invalid before the gateway can contact the upstream.Fix policies.json or routes.oas.json. The error should name the broken entry.
Upstream OAuth setupThe upstream MCP server requires provider/admin setup that the gateway cannot complete automatically.Configure the provider app, allowlist redirect URIs, add required scopes, or contact the provider to approve the client.
Upstream service responseThe upstream server returned its own error page or OAuth error.Treat the upstream response as the source of truth and fix the upstream URL, allowlist, account region, or provider configuration.

For browser-based OAuth failures, the gateway error page shows a user-friendly message and visible developer details. Developer details include the gateway error code, request id, and the underlying reason so screenshots are useful in support tickets.

If the upstream returns an HTML error response, such as an edge firewall 403 Access Denied page, the gateway displays that upstream HTML response on the error page instead of replacing it with only a generic gateway message. This usually means the upstream URL is not publicly reachable from the gateway or the provider has not allowed this client/network/account to access the MCP endpoint.

Common examples:

  • Provider requires app approval or allowlisting: the upstream may reject DCR/CIMD or only allow registered clients. Configure a provider OAuth app or contact the provider to approve the client.
  • Provider requires explicit scopes: add the provider-required scopes to scopes. Do not rely on gateway inference when the provider does not publish the required values.
  • Wrong or private upstream URL: if a direct probe of the upstream MCP URL returns an HTML 403, 404, or branded provider error before OAuth discovery, fix the rewritePattern/metadata URL or provider access. The gateway cannot make a private or blocked upstream public.
  • No upstream auth: omit this policy for anonymous MCP servers. A public upstream should route through McpProxyHandler without token exchange.

Read more about how policies work

Edit this page
Last modified on June 15, 2026
MCP WorkOS OAuthRate Limiting
On this page
  • Configuration
    • Policy Configuration
    • Policy Options
  • Using the Policy
  • Overview
  • Configuration
  • Scope Selection
  • Route Shape
  • Troubleshooting Upstream OAuth
JSON
JSON
JSON