> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.com/ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Call Your API on a User's Behalf

> Learn how to call an Auth0-protected API from your MCP server.

export const DownloadQuickstartButton = ({category, framework, label = "Download Sample"}) => {
  const [isDownloading, setIsDownloading] = useState(false);
  const githubRepo = 'auth0-samples/auth0-ai-samples';
  const filename = `${category}-${framework}-sample.zip`;
  const downloadSample = () => {
    setIsDownloading(true);
    const downloadUrl = `https://github.com/${githubRepo}/releases/latest/download/${filename}`;
    window.open(downloadUrl, '_blank');
    setTimeout(() => setIsDownloading(false), 1000);
  };
  return <div className="download-button-container">
      <button onClick={downloadSample} disabled={isDownloading} className={`download-button ${isDownloading ? 'downloading' : ''}`} aria-label={`Download ${category} ${framework} sample code`}>
        {isDownloading ? <>
            <svg className="download-spinner" width="16" height="16" viewBox="0 0 24 24" fill="none">
              <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" fill="none" strokeDasharray="31.416" strokeDashoffset="31.416">
                <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite" />
                <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite" />
              </circle>
            </svg>
            Downloading...
          </> : <>
            <Icon icon="download" color="white" />
            {label}
          </>}
      </button>
    </div>;
};

Auth0 implements the On-Behalf-Of (OBO) token exchange ([RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html)) to enable MCP servers to preserve user identity and permissions when calling downstream APIs. In the OBO token exchange flow, the MCP server acts in a dual role: as a resource server when receiving requests from the client, and as a client when calling your downstream API on the user’s behalf.

To call your APIs on a user’s behalf, the MCP server must exchange the Auth0 access token it receives from the MCP client for a new access token with a different audience. The original token has the MCP server as its audience, while the new token has your downstream API as its audience.

<Frame>
  <img className="hidden dark:block" src="https://mintlify.s3.us-west-1.amazonaws.com/auth0-genai/img/mcp/mcp_call_api_obo_exchange_dark.png" alt="MCP Diagram" />

  <img className="block dark:hidden" src="https://mintlify.s3.us-west-1.amazonaws.com/auth0-genai/img/mcp/mcp_call_api_obo_exchange_light.png" alt="MCP Diagram" />
</Frame>

By the end of this quickstart, you will have:

* An MCP server that calls your protected APIs on behalf of authenticated users using the OBO token exchange
* Token verification and exchange using the `@auth0/auth0-api-js` library (JavaScript) or `auth0-api-python` library (Python)
* A working example demonstrating the end-to-end OBO token exchange flow from client to MCP server to downstream API

## Prerequisites

<AccordionGroup>
  <Accordion title="1. Create or log in to your Auth0 account">
    To continue with this quickstart, you need to have an [Auth0 account](https://auth0.com/signup).
  </Accordion>

  <Accordion title="2. Enable the Resource Parameter Compatibility Profile and Include Issuer in Authorization Responses">
    To use the `resource` parameter in your access tokens and the `iss` claim to identify your authorization server, you need to enable two toggles in the [Auth0 Dashboard](https://manage.auth0.com/dashboard/):

    1. Navigate to **Settings** on the left sidebar.
    2. Select [**Advanced**](https://manage.auth0.com/dashboard/#/tenant/advanced) in the top right corner.
    3. Scroll down to the **Settings** section and enable the following toggles:
       * **Resource Parameter Compatibility Profile**: Enables support for the `resource` parameter in authorization requests, allowing access tokens to be scoped to a specific API.
       * **Include Issuer in Authorization Responses**: Includes the `iss` parameter identifying the authorization server in authorization responses, helping clients defend against mix-up attacks.
  </Accordion>

  <Accordion title="3. Install the Auth0 CLI">
    This guide uses [Auth0 CLI](https://auth0.github.io/auth0-cli/) to configure an Auth0 tenant for secure MCP tool access.

    1. Follow the [Auth0 CLI installation instructions](https://auth0.github.io/auth0-cli/).
    2. Log in to your account with the Auth0 CLI:

    ```shell wrap lines theme={null}
    auth0 login --scopes "read:client_grants,create:client_grants,delete:client_grants,read:clients,create:clients,update:clients,read:resource_servers,create:resource_servers,update:resource_servers,read:roles,create:roles,update:roles,update:tenant_settings,read:connections,update:connections"
    ```

    Confirm you are in the correct tenant by double-checking the tenant domain in the terminal, or run the command below:

    ```shell theme={null}
    auth0 tenants list
    ```

    If more than one tenant is configured, the default tenant will be indicated by an arrow.
  </Accordion>

  <Accordion title="4. Install jq">
    To simplify the process of interacting with the Auth0 CLI, we recommend installing [jq](https://jqlang.org/download/). This will allow you to easily parse JSON responses from the CLI.

    <Tabs>
      <Tab title="macOS">
        <CodeBlock language="shell">brew install jq</CodeBlock>
      </Tab>

      <Tab title="Linux">
        <CodeBlock language="shell">sudo apt-get install jq</CodeBlock>
      </Tab>

      <Tab title="Windows">
        <CodeBlock language="shell">choco install jq</CodeBlock>
      </Tab>
    </Tabs>
  </Accordion>
</AccordionGroup>

## Configure tenant settings

<AccordionGroup>
  <Accordion title="1. Promote the connection to a domain-level connection">
    To allow third-party clients like MCP Inspector to use a connection such as a username-and-password database or a social connection, you need to promote the connection to a domain-level connection. [Learn more about configuring third-party applications](https://auth0.com/docs/get-started/applications/third-party-applications/configure-third-party-applications).

    <Steps>
      <Step title="List Your Connection">
        List your connections to get their IDs

        ```shell theme={null}
        auth0 api get connections
        ```
      </Step>

      <Step title="Choose Your Connections">
        From the list, identify which connections you want to use for the MCP server and copy the ID.

        * For the username-and-password database, look for the connection with the strategy `auth0`.
        * For the Google social connection, look for the connection with the strategy `google-oauth2`.
      </Step>

      <Step title="Upgrade the Connection to Domain-Level">
        For each of those specific connection IDs, run the following command to mark it as a domain-level connection. Replace `YOUR_CONNECTION_ID` with the actual ID (e.g., `con_XXXXXXXXXXXXXXXX`).

        ```shell wrap lines theme={null}
        auth0 api patch connections/YOUR_CONNECTION_ID --data '{"is_domain_connection": true}'
        ```
      </Step>
    </Steps>
  </Accordion>

  <Accordion title="2. Create roles">
    The sample application in this quickstart is prepared to show different tools depending on the role of a given user. For example, a Tool Administrator will have access to different tools than a Tool User.

    Let's create roles that allow you to control which users can access which tools.

    For each role you need (e.g., "Tool Administrator", "Tool User"), run the create command as below.

    ```shell wrap lines theme={null}
    # Example for an admin role
    auth0 roles create --name "Tool Administrator" --description "Grants access to all MCP tools"

    # Example for a basic user role
    auth0 roles create --name "Tool User" --description "Grants access to basic MCP tools"
    ```

    Save the IDs from the output (they start with `rol_`), as you'll need them in a later step to assign permissions and users to these roles.
  </Accordion>

  <Accordion title="3. Assign roles to users">
    Find users and assign them to the roles. You can search by their email address:

    ```shell wrap lines theme={null}
    auth0 users search --query "email:\"example@google.com\""
    ```

    Copy the user ID (`USERID`) from the last command's output, then assign the role to the user:

    ```shell wrap lines theme={null}
    auth0 users roles assign "USER_ID_HERE" --roles "YOUR_ROLE_ID_HERE"
    ```
  </Accordion>
</AccordionGroup>

## Set up the Auth0 applications and APIs

The MCP server's dual role requires two configurations in Auth0:

1. As a resource server: An API that the MCP client calls
2. As a client: An application that can exchange tokens to call downstream APIs

Let's set up both configurations.

### Create an API to represent your MCP server

An MCP server is treated just like any other [API](https://auth0.com/docs/get-started/apis) in your Auth0 tenant, which means you can use the same access control, scoping, and authorization features. The API (also known as a Resource Server) represents your protected MCP Server.

Run the following command to [create an API](https://auth0.com/docs/get-started/auth0-overview/set-up-apis) in Auth0:

```shell wrap lines theme={null}
auth0 api post resource-servers --data '{
  "identifier": "http://localhost:3001/",
  "name": "MCP Tools API",
  "signing_alg": "RS256",
  "token_dialect": "rfc9068_profile_authz",
  "enforce_policies": true,
  "scopes": [
    {"value": "tool:whoami", "description": "Access the WhoAmI tool"},
    {"value": "tool:greet", "description": "Access the Greeting tool"}
  ]
}'
```

Note that `rfc9068_profile_authz` is used as the [token dialect](https://auth0.com/docs/get-started/apis/enable-role-based-access-control-for-apis#token-dialect-options) to include the `permissions` claim in the access token, which can be useful for the API to check user permissions without making additional calls.

For more details on protecting APIs with Auth0, check our [quickstarts](https://auth0.com/docs/quickstarts#backend%2Fapi).

<Accordion title="Assign permissions to roles">
  After creating the API and roles, assign the API permissions to the roles.

  ```shell wrap lines theme={null}
  # Example for admin role (all scopes)
  auth0 roles permissions add YOUR_ADMIN_ROLE_ID --api-id "http://localhost:3001/" --permissions "tool:whoami,tool:greet"

  # Example for user role (one scope)
  auth0 roles permissions add YOUR_USER_ROLE_ID --api-id "http://localhost:3001/" --permissions "tool:whoami"
  ```

  Replace `YOUR_ADMIN_ROLE_ID` and `YOUR_USER_ROLE_ID` with the role IDs you saved when creating the roles.
</Accordion>

### Create an application for your MCP server

Create an application that allows your MCP server to act as a client for the OBO token exchange. This application must:

* Have the same identifier as your MCP server API (`resource_server_identifier`)
* Use `app_type: resource_server` to link it to the MCP server
* Enable OBO token exchange in the `token_exchange` configuration

The following command creates an application linked to your MCP server with OBO token exchange enabled:

```shell wrap lines theme={null}
auth0 api post clients --data '{
  "name": "MCP Server Client",
  "app_type": "resource_server",
  "oidc_conformant": true,
  "resource_server_identifier": "http://localhost:3001/",
  "token_exchange": {
    "allow_any_profile_of_type": ["on_behalf_of_token_exchange"]
  }
}' | jq -c '{client_id, client_secret}' > auth0-app-details.json
```

Save the `client_id` and `client_secret` from the output; you'll need them to configure your MCP server environment.

### Create a downstream API

Create the protected API that your MCP server will call on the user's behalf. This API represents your business logic API (e.g., a calendar API, document API, etc.).

The following command creates an API with the following configurations:

* Client grants: Gives the MCP server [user-delegated and client access](https://auth0.com/docs/get-started/applications/application-access-to-apis-client-grants) the API
* Scope enforcement: Requires specific permissions (`read:private`)
* First-party status: Skips user consent for your own applications

```shell theme={null}
auth0 api post resource-servers --data '{
  "identifier": "http://localhost:8787/",
  "name": "Protected Downstream API",
  "signing_alg": "RS256",
  "enforce_policies": true,
  "scopes": [
    {"value": "read:private", "description": "Private scope"}
  ],
  "subject_type_authorization": {
    "user": {
        "policy": "require_client_grant"
    },
    "client": {
        "policy": "require_client_grant"
    }
  },
  "skip_consent_for_verifiable_first_party_clients": false
}'
```

Save the API identifier (audience) from the output; you'll configure it in your environment variables as `API_AUTH0_AUDIENCE`.

<Accordion title="Assign permissions to roles">
  After creating the downstream API and roles, assign the `read:private` permission to the roles.

  ```shell wrap lines theme={null}
  # Example for admin role
  auth0 roles permissions add YOUR_ADMIN_ROLE_ID --api-id "http://localhost:8787/" --permissions "read:private"

  # Example for user role
  auth0 roles permissions add YOUR_USER_ROLE_ID --api-id "http://localhost:8787/" --permissions "read:private"
  ```

  Replace `YOUR_ADMIN_ROLE_ID` and `YOUR_USER_ROLE_ID` with the role IDs you saved when creating the roles.
</Accordion>

## Sample app

<Tabs>
  <Tab title="Javascript" icon="js">
    <Tabs>
      <Tab title="Use sample app (recommended)">
        Start by downloading the sample app for this quickstart. The sample includes a FastMCP MCP server with an Auth0 integration and a protected API built with
        [Fastify](https://fastify.dev/).

        <DownloadQuickstartButton category="auth-for-mcp" framework="fastmcp-mcp-on-behalf-of-tokenexchange-js" />

        Once downloaded, extract the files and open the project in your preferred IDE.
      </Tab>

      <Tab title="Clone GitHub repository">
        Clone the repository and navigate to the sample app folder, which includes a FastMCP MCP server with an Auth0 integration and a protected API built with
        [Fastify](https://fastify.dev/).

        ```shell wrap lines theme={null}
        git clone https://github.com/auth0-samples/auth0-ai-samples.git
        cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-on-behalf-of-tokenexchange-js
        ```

        Once cloned, open the project in your preferred IDE.
      </Tab>
    </Tabs>

    The sample app demonstrates the complete OBO flow with a `greet` tool that:

    1. Receives an authenticated request from an MCP client
    2. Exchanges the token for downstream API access
    3. Calls your protected API on the user's behalf
    4. Returns the result

    ## Install packages

    Make sure you have [npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Then, in the `fastmcp-mcp-on-behalf-of-tokenexchange-js` directory, install the required packages:

    ```shell theme={null}
    npm install
    ```

    ## Create your environment file

    From the root of your sample app directory, run the following command to create a new `.env` file populated with all the required environment variables:

    ```shell wrap lines expandable theme={null}
    CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) \
    && CLIENT_SECRET=$(jq -r '.client_secret' auth0-app-details.json) \
    && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') \
    && touch .env \
    && echo "AUTH0_DOMAIN=${DOMAIN}" > .env \
    && echo "AUTH0_AUDIENCE=http://localhost:3001/" >> .env \
    && echo "PORT=3001" >> .env \
    && echo "MCP_SERVER_URL=http://localhost:3001/" >> .env \
    && echo "MCP_AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env \
    && echo "MCP_AUTH0_CLIENT_SECRET=${CLIENT_SECRET}" >> .env \
    && echo "MCP_AUTH0_EXCHANGE_SCOPE=read:private" >> .env \
    && echo "API_AUTH0_AUDIENCE=http://localhost:8787/" >> .env \
    && echo "API_BASE_URL=http://localhost:8787" >> .env \
    && rm auth0-app-details.json \
    && echo ".env file created with your Auth0 details:" \
    && cat .env
    ```

    ## Run the MCP server and the API

    Run this command to start your server:

    ```shell theme={null}
    npm run start
    ```

    And in a separate window run this command to start the API:

    ```shell theme={null}
    npm run start:api
    ```

    ## Exchange your access token

    To call your APIs on a user's behalf, the MCP server uses the OBO token exchange to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API.

    ### Token exchange implementation

    The MCP server exchanges tokens in two steps:

    #### 1. Wrapper function: `bearerForUpstream()`

    This function safely handles the token exchange process and error handling:

    ```javascript wrap lines highlight={5} theme={null}
    async function bearerForUpstream(accessToken: string) {
      if (!accessToken) return { token: null, scopes: null };

      try {
        const result = await exchangeTokenOnBehalfOf(accessToken);
        return {
          token: result.accessToken,
          scopes: result.scope,
        }
      } catch (err) {
        console.error('Error during token exchange:', err);
        throw err;
      }
    }
    ```

    `bearerForUpstream()` calls `exchangeTokenOnBehalfOf()` and, upon a successful exchange, returns the new `accessToken` and its associated scope. If the exchange fails, it logs the error and re-throws it to be handled upstream.

    #### 2. Exchange function: `exchangeTokenOnBehalfOf()`

    This function, located in `src/auth0.ts`, calls Auth0's OBO token exchange endpoint using the SDK.

    First, initialize the `ApiClient` with the credentials of the MCP server application:

    ```javascript wrap lines theme={null}
    const apiClient = new ApiClient({
      domain: AUTH0_DOMAIN,
      audience: AUTH0_AUDIENCE,
      clientId: MCP_AUTH0_CLIENT_ID,
      clientSecret: MCP_AUTH0_CLIENT_SECRET,
    });
    ```

    Once you've configured the MCP server application, the `exchangeTokenOnBehalfOf()` function uses the client's `getTokenOnBehalfOf()` method to perform the token exchange.

    `getTokenOnBehalfOf()` implements the OBO token exchange flow, which allows the MCP server to obtain a new token to call the downstream API while preserving the user's identity. It takes in the `accessToken`, `audience`, and `scope` parameters.

    ```javascript wrap lines theme={null}
    export async function exchangeTokenOnBehalfOf(accessToken: string) {
        return await apiClient.getTokenOnBehalfOf(accessToken, {
          audience: API_AUTH0_AUDIENCE,
          ...(MCP_AUTH0_EXCHANGE_SCOPE && { scope: MCP_AUTH0_EXCHANGE_SCOPE }),
        });
    }
    ```
  </Tab>

  <Tab title="Python" icon="python">
    <Tabs>
      <Tab title="Use sample app (recommended)">
        Start by downloading the sample app for this quickstart. The sample includes a FastMCP MCP server with an Auth0 integration and a protected Starlette-based API.

        <DownloadQuickstartButton category="auth-for-mcp" framework="fastmcp-mcp-on-behalf-of-tokenexchange-python" />

        Once downloaded, extract the files and open the project in your preferred IDE.
      </Tab>

      <Tab title="Clone GitHub repository">
        Clone the repository and navigate to the sample app folder which includes a FastMCP MCP server with an Auth0 integration and a protected Starlette-based API.

        ```shell wrap lines theme={null}
        git clone https://github.com/auth0-samples/auth0-ai-samples.git
        cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-on-behalf-of-tokenexchange-python
        ```

        Once cloned, open the project in your preferred IDE.
      </Tab>
    </Tabs>

    The sample app demonstrates the complete OBO flow with a `greet` tool that:

    1. Receives an authenticated request from an MCP client
    2. Exchanges the token for downstream API access
    3. Calls your protected API on the user's behalf
    4. Returns the result

    ## Install packages

    Make sure you have [poetry installed](https://python-poetry.org/docs/). Then, in the `fastmcp-mcp-on-behalf-of-tokenexchange-python` directory, install the required packages:

    ```shell theme={null}
    poetry install
    ```

    ## Create your environment file

    From the root of your sample app directory, run the following command to create a new `.env` file populated with all the required environment variables:

    ```shell wrap lines expandable theme={null}
    CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) \
    && CLIENT_SECRET=$(jq -r '.client_secret' auth0-app-details.json) \
    && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') \
    && touch .env \
    && echo "AUTH0_DOMAIN=${DOMAIN}" > .env \
    && echo "AUTH0_AUDIENCE=http://localhost:3001/" >> .env \
    && echo "PORT=3001" >> .env \
    && echo "MCP_SERVER_URL=http://localhost:3001/" >> .env \
    && echo "MCP_AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env \
    && echo "MCP_AUTH0_CLIENT_SECRET=${CLIENT_SECRET}" >> .env \
    && echo "MCP_AUTH0_EXCHANGE_SCOPE=read:private" >> .env \
    && echo "API_AUTH0_AUDIENCE=http://localhost:8787/" >> .env \
    && echo "API_BASE_URL=http://localhost:8787" >> .env \
    && rm auth0-app-details.json \
    && echo ".env file created with your Auth0 details:" \
    && cat .env
    ```

    ## Run the MCP server and the API

    Run this command to start your server:

    ```shell theme={null}
    poetry run python -m src.server
    ```

    And in a separate window run this command to start the API:

    ```shell theme={null}
    poetry run python -m src.api.server
    ```

    ## Exchange your access token

    To call your APIs on a user's behalf, the MCP server uses the OBO token exchange to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API.

    ### How tools use token exchange

    Here's how the `greet` tool performs the token exchange and calls the upstream API:

    ```python wrap lines highlight={10,11,12,13,14,17} theme={null}
    @mcp.tool(name="greet")
    @require_scopes(["tool:greet"])
    async def greet(name: str, ctx: Context) -> str:
        user_name = name.strip() if name else "there"
        auth_info = ctx.request_context.request.state.auth
        user_id = auth_info.get("extra", {}).get("sub")

        logger.info(f"Greet tool invoked for user: {user_id}")

        # Exchange token and call upstream API
        exchange_result = await exchange_token_on_behalf_of(
            ctx.request_context.request.state.api_client,
            auth_info["token"]
        )

        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{config.api_base_url}/api/private-scope",
                headers={"authorization": f"Bearer {exchange_result['token']}"}
            )
            upstream_result = response.json()

        return f"Hello, {user_name} ({user_id})!\nUpstream API Response: {json.dumps(upstream_result, indent=2)}"
    ```

    ### The core logic: `exchange_token_on_behalf_of()`

    The Python implementation uses the `exchange_token_on_behalf_of()` function that handles the token exchange:

    ```python wrap lines highlight={3,4,5,6} theme={null}
    async def exchange_token_on_behalf_of(api_client, access_token: str) -> dict:
        """Exchange access token for downstream API token via On-Behalf-Of Token Exchange."""
        result = await api_client.get_token_on_behalf_of(
            access_token=access_token,
            audience=config.api_auth0_audience,
            scope=config.mcp_auth0_exchange_scope or None
        )
        return {"token": result["access_token"], "scopes": result.get("scope", "")}
    ```

    `exchange_token_on_behalf_of()` uses the `get_token_on_behalf_of()` method of `ApiClient` from the `auth0-api-python` SDK. It takes in the `access_token`, `audience`, and optional `scope` parameters, and upon a successful exchange, returns the new access token and its associated scopes.

    ### Client configuration

    The `ApiClient` is initialized in the `Auth0Middleware` (located in `src/auth0/middleware.py`) with the credentials of the MCP server application:

    ```python src/auth0/middleware.py wrap lines theme={null}
    self.client = ApiClient(ApiClientOptions(
        domain=domain, # AUTH0_DOMAIN env var
        audience=audience, # AUTH0_AUDIENCE env var
        client_id=client_id, # MCP_AUTH0_CLIENT_ID env var
        client_secret=client_secret # MCP_AUTH0_CLIENT_SECRET env var
    ))
    ```

    The configured client is then attached to the request context during middleware processing, making it available to all tools:

    ```python src/auth0/middleware.py wrap lines theme={null}
    # In the Auth0Middleware dispatch method
    request.state.api_client = self.client
    ```
  </Tab>
</Tabs>

## Testing your MCP server

Now that your MCP server is up and running, you can test it by using tools like [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).

<Card title="Learn how to test your Auth0-powered MCP server" href="/ai/docs/mcp/guides/test-your-mcp-server-with-mcp-inspector" icon="book" iconType="solid" horizontal />

## Next steps

* To set up MCP server authorization, complete the [Authorization for Your MCP Server](./authorization-for-your-mcp-server) quickstart.
