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
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
    IntroductionBetaQuickstartQuickstart (Local Dev)How it works
    Connect MCP clients
    Authentication
    Cross App Access
      OverviewQuickstartConfiguration reference
    Configuration
    Observability
    ReferenceTroubleshooting
AI Gateway
Developer Portal
Monetization
GraphQL
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Cross App Access

Cross App Access quickstart

This quickstart builds a working Cross App Access (XAA) gateway entirely in the Zuplo Portal. An MCP client connects to the gateway with ordinary OAuth; the gateway then performs the XAA token exchange against Okta's hosted xaa.dev playground and reaches the protected Todo0 MCP server on the user's behalf.

There is no repository to clone and no identity tenant to stand up. Everything runs in the portal against the public xaa.dev playground.

Why xaa.dev

xaa.dev is Okta's hosted Cross App Access playground: a public identity provider, resource authorization server, and Todo0 MCP server that are already wired together for the XAA token exchange. Standing up your own XAA stack means provisioning an IdP, a resource authorization server, and a protected MCP server — and configuring the trust relationships between all three. The playground gives you all of that for free, so today it's the fastest way to see a real Cross App Access flow end to end. Once the gateway works against xaa.dev, swapping in your own identity provider and upstream is just a change of endpoints and credentials.

What you'll build

A single gateway route with two policies does all the work:

  • An MCP OAuth policy (mcp-oauth-inbound) secures the client → gateway leg. The client authenticates to the gateway with plain MCP OAuth — this leg is not XAA.
  • A token-exchange policy (mcp-token-exchange-inbound) in id-jag mode performs the outbound XAA exchange to the upstream: it mints an ID-JAG at the playground identity provider (IdenX), redeems it at the resource authorization server, and calls the Todo0 MCP server with the resulting access token.

The XAA exchange happens entirely on the gateway's outbound side. See the overview for the full sequence.

Prerequisites

  • A Zuplo account
  • A free account on xaa.dev
  • MCP Jam — a browser-based MCP client used to drive the flow. Any MCP client that supports OAuth works.

Steps

  1. Create a Zuplo project.

    In the Zuplo Portal, select New Project and create an empty API Gateway project. Name it xaa-quick-start.

    On the project Overview page, copy the deployment URL shown at the top (for example, https://xaa-quick-start-main-abc1234.d2.zuplo.dev). This is your gateway's public origin — you'll need it for the next two steps.

    Throughout this guide, replace https://your-gateway.zuplo.dev with your project's actual deployment URL.

  2. Register a requesting app on xaa.dev.

    Sign in to xaa.dev and open the developer registration page. Enter your email, then select Register New App and fill in:

    • Application Name — anything, for example xaa-quick-start gateway.

    • Redirect URIs — your gateway's OAuth callback:

      Code
      https://your-gateway.zuplo.dev/__zuplo/oauth/callback

    Under Resource Connections, select Todo0 MCP Server, keep both scopes (todos.read and mcp.access) checked, and select Add Connection. Then select Register App.

    https://xaa.dev/developer/register

    Registered requesting app on xaa.dev

    Registration shows four values. Copy all of them — the secrets are shown only once:

    xaa.dev valueUsed for
    Client IDXAA_CLIENT_ID
    Client SecretXAA_CLIENT_SECRET
    Resource Client IDXAA_RESOURCE_AS_CLIENT_ID
    Resource Client SecretXAA_RESOURCE_AS_CLIENT_SECRET

    The playground identity provider (IdenX) accepts any email with no password, so you can sign in as any test user when you drive the flow.

  3. Add the gateway configuration.

    Open the project's code editor (the Code tab). The gateway needs three files.

    First, define the two policies. Open config/policies.json (use the raw policies.json tab, not the visual Policy List) and replace its contents:

    config/policies.json
    { "policies": [ { "name": "xaa-inbound", "policyType": "mcp-oauth-inbound", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpOAuthInboundPolicy", "options": { "oidc": { "issuer": "https://idp.xaa.dev", "jwksUrl": "https://idp.xaa.dev/jwks", "audience": "$env(GATEWAY_AUDIENCE)" }, "browserLogin": { "url": "https://idp.xaa.dev/authorize", "tokenUrl": "https://idp.xaa.dev/token", "clientId": "$env(XAA_CLIENT_ID)", "clientSecret": "$env(XAA_CLIENT_SECRET)", "scope": "openid profile email", "audience": "$env(GATEWAY_AUDIENCE)", "pkce": "S256" } } } }, { "name": "id-jag-upstream", "policyType": "mcp-token-exchange-inbound", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpTokenExchangeInboundPolicy", "options": { "id": "id-jag-upstream", "displayName": "Todo0", "summary": "xaa.dev Todo0 MCP server, reached via Cross App Access (ID-JAG).", "authMode": "id-jag", "idJag": { "scopes": ["todos.read", "mcp.access"], "idp": { "tokenUrl": "https://idp.xaa.dev/token", "clientAuth": { "method": "client_secret_post", "clientId": "$env(XAA_CLIENT_ID)", "clientSecret": "$env(XAA_CLIENT_SECRET)" } }, "resourceAs": { "tokenUrl": "https://auth.resource.xaa.dev/token", "audience": "https://auth.resource.xaa.dev", "resource": "https://mcp.xaa.dev/mcp", "clientAuth": { "method": "client_secret_post", "clientId": "$env(XAA_RESOURCE_AS_CLIENT_ID)", "clientSecret": "$env(XAA_RESOURCE_AS_CLIENT_SECRET)" } } } } } } ] }

    The IdP, resource authorization server, and upstream endpoints are wired to the playground here. Only the credentials and the gateway audience are environment-driven, so you can plug in your own xaa.dev app. For every option, see the configuration reference.

    Next, add the route that exposes the gateway. Open config/routes.oas.json (the raw routes.oas.json tab) and replace its contents:

    config/routes.oas.json
    { "openapi": "3.1.0", "info": { "version": "1.0.0", "title": "XAA MCP Gateway", "description": "MCP gateway that bridges to the xaa.dev Todo0 MCP server via Cross App Access (ID-JAG)." }, "paths": { "/mcp/todo0": { "post": { "operationId": "todo0Bridge", "summary": "Bridge to the xaa.dev Todo0 MCP server", "x-zuplo-route": { "corsPolicy": "none", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpProxyHandler", "options": { "rewritePattern": "https://mcp.xaa.dev/mcp" } }, "policies": { "inbound": ["xaa-inbound", "id-jag-upstream"] } } } } } }

    The McpProxyHandler forwards the request to the upstream Todo0 server, and both policies run inbound — first the MCP OAuth check, then the XAA exchange.

    Finally, register the MCP Gateway plugin. In the file tree, right-click the modules folder, select New Runtime Extension, and replace the generated file's contents:

    modules/zuplo.runtime.ts
    import { RuntimeExtensions } from "@zuplo/runtime"; import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway"; // Registers the MCP Gateway, which adds the OAuth and upstream-connection // routes used to expose and secure MCP servers through your gateway. // Docs: https://zuplo.com/docs/mcp-server/introduction export function runtimeInit(runtime: RuntimeExtensions) { runtime.addPlugin(new McpGatewayPlugin()); }

    Save each file. The build fails until the environment variables exist — that's the next step.

  4. Set the environment variables.

    Open Settings → Environment Variables and add each variable below with Add variable. Mark the two *_SECRET values as Secret so they're encrypted and hidden after saving. Leave all three environments (Production, Preview, Development) selected.

    VariableValueSecret
    GATEWAY_AUDIENCEYour gateway's deployment URL (for example, https://your-gateway.zuplo.dev)No
    XAA_CLIENT_IDClient ID from the xaa.dev appNo
    XAA_CLIENT_SECRETClient Secret from the xaa.dev appYes
    XAA_RESOURCE_AS_CLIENT_IDResource Client ID from the Todo0 connectionNo
    XAA_RESOURCE_AS_CLIENT_SECRETResource Client Secret from the Todo0 connectionYes

    Saving an environment variable triggers a new deployment. Once it finishes, the gateway is live.

  5. Connect with MCP Jam and list the todos.

    Open the MCP Jam web inspector, go to Connect, and select Add Server:

    • Server Name — xaa-quick-start
    • Connection Type — HTTPS, with the route URL https://your-gateway.zuplo.dev/mcp/todo0
    • Authentication — OAuth

    Add the gateway as an MCP server in MCP Jam

    Select Add Server. MCP Jam starts the OAuth flow and redirects you to sign in. Sign in at the IdenX screen (any email, no password) and approve the consent screen. MCP Jam returns to the inspector and the server shows as Connected.

    The gateway connected in MCP Jam

    Open the Resources panel and select List all todos. The todos come back as JSON — the agent never touched the XAA protocol. Behind the scenes the gateway minted an ID-JAG from the playground IdP, redeemed it at the resource authorization server, and called the Todo0 MCP server with the resulting access token.

    Todo0 resources and todos returned through the gateway in MCP Jam

Verify from the command line (optional)

To confirm the gateway is deployed and secured without a client, check its OAuth metadata and the protected route:

TerminalCode
# Protected-resource metadata is published for the route (expect 200) curl -s https://your-gateway.zuplo.dev/.well-known/oauth-protected-resource/mcp/todo0 # The MCP route rejects unauthenticated calls (expect 401) curl -s -o /dev/null -w "%{http_code}\n" -X POST \ https://your-gateway.zuplo.dev/mcp/todo0 \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1.0.0"}}}'

Next steps

  • Configuration reference — the full idJag option set.
  • Overview — the protocol and the gateway's role explained.
Edit this page
Last modified on June 22, 2026
OverviewConfiguration reference
On this page
  • Why xaa.dev
  • What you'll build
  • Prerequisites
  • Steps
  • Verify from the command line (optional)
  • Next steps
JSON
JSON
TypeScript