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

# Call First-Party APIs on a User's Behalf

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

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 first-party 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.

![MCP Diagram](https://mintlify.s3.us-west-1.amazonaws.com/auth0/docs/images/ai/mcp/mcp_call_api_obo_exchange_light.png)

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

> **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).

> **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.

> **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.

> **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.
>
> **macOS**
>
> brew install jq
>
> **Linux**
>
> sudo apt-get install jq
>
> **Windows**
>
> choco install jq

## Configure tenant settings

> **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.md).
>
> **1. List Your Connection**
>
> List your connections to get their IDs
>
> ```shell theme={null}
> auth0 api get connections
> ```
>
> **2. 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`.
>
> **3. 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}'
> ```

> **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.

> **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"
> ```

## 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.md) 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.md#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.md#backend%2Fapi).

> **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.

### 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.md) 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`.

> **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.

## Sample app

**Javascript**

**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/).

Download auth-for-mcp fastmcp-mcp-on-behalf-of-tokenexchange-js sample code

Once downloaded, extract the files and open the project in your preferred IDE.

**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.

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 }),
    });
}
```

**Python**

**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.

Download auth-for-mcp fastmcp-mcp-on-behalf-of-tokenexchange-python sample code

Once downloaded, extract the files and open the project in your preferred IDE.

**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.

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
```

## 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).

> **[Learn how to test your Auth0-powered MCP server](https://auth0.com/ai/docs/mcp/guides/test-your-mcp-server-with-mcp-inspector.md)**

## Next steps

* To set up MCP server authorization, complete the [Authorization for Your MCP Server](https://auth0.com/ai/docs/mcp/get-started/authorization-for-your-mcp-server.md) quickstart.
