---
title: "Protect Your MCP Tools With Auth0 FGA in TypeScript"
description: "Learn how to secure your Model Context Protocol (MCP) tools using Auth0 FGA and TypeScript. Implement relationship-based access control for AI applications."
authors:
  - name: "Raphael do Vale"
    url: "https://auth0.com/blog/authors/raphael-do-vale/"
date: "Mar 2, 2026"
category: "Developers,Tutorial"
tags: ["fga", "mcp"]
url: "https://auth0.com/blog/protect-mcp-tools-with-auth0-fga-in-typescript/"
---

# Protect Your MCP Tools With Auth0 FGA in TypeScript

[Model Context Protocol](https://modelcontextprotocol.io/) (MCP) is an open standard for connecting AI applications to external systems. Much like a self-describing API, an MCP server can be deployed and connected by anyone, increasing the capacity of an LLM in terms of what it can do.

[Fine Grained Authorization (FGA)](https://fga.dev/) is a tool that allows you to define fine-grained permissions based on the relationship between objects. Inspired by [Google's Zanzibar](https://zanzibar.academy/), [OpenFGA](https://openfga.dev/) is an open-source authorization solution for defining fine-grained permissions based on Relationship-Based Access Control (ReBAC). OpenFGA is the engine of [Auth0 FGA](https://fga.dev/) and similar solutions.

In this article, we will use [TypeScript's FastMCP](https://github.com/modelcontextprotocol/typescript-sdk) to create an MCP Server that uses Auth0 to authenticate and control access, and Auth0 FGA to define the permissions. By the end, our MCP Server will be able to show only the tools that the user can use through an LLM connecting via an MCP server. In addition, you will add granular filtering, allowing documents to be shown only to users with access to them. In other words: our MCP server will behave differently depending on the permissions of the user. 

## Prerequisites

We are going to use the following:

* Node.js 18+
* npm
* An Auth0 tenant. Create a free account [here](https://auth0.com/signup).
* An Auth0 FGA account. You can create a free account [here](https://fga.dev).
* Our Git repository. [This repository](https://github.com/auth0-samples/auth0-fastmcp-fga) contains all the code for this tutorial and a start application you will use.

This is an introductory tutorial to MCP and FGA, so no previous knowledge in these are required. Beginner level of Node and TypeScript knowledge is ideal.

## The Application You Will Build

For this tutorial, you’ll use the FastMCP library to build a TypeScript MCP Server and learn how MCP works and how we can integrate FGA with it to provide granular access control. The MCP server will have three tools:

* `get_whoami`: will tell who the logged-in user is.
* `get_datetime`: will return the current date and time, which helps the LLM to have some context
* `get_documents`: this is our real feature and will list the documents in our library. The documents can be public or private and will be filtered based on the user permissions using FGA.

First, we will implement these tools without any kind of security, just showing you how we can use MCP and how we can debug it, then you’ll add the access control layer.

### Setup the code

We need to bootstrap our project with dependencies and folder structure. To facilitate your journey, we created a repository so you can follow along. To begin, clone the repository if you haven’t done so and  switch to the `01-starter` branch. This branch have the initial setup and all dependencies needed for this tutorial:

```bash
git switch 01-starter
```

Now install the dependencies using `npm`:

```bash
npm install
```

At this branch, we have this folder structure:

```bash
   root
   |- src
       |- index.ts
       |- documentApi.ts
   |- package.json
   |- package-lock.json
   |- tsconfig.json
```

For the dependencies were are using:

* `@auth0/auth0-api-js`: Auth0’s JavaScript SDK for calling Auth0 APIs (management/auth flows) from your app/server.
* `@modelcontextprotocol/sdk`: Core MCP toolkit to implement an MCP server: define tools, handle requests, and talk MCP reliably.
* `@openfga/sdk`: Client SDK for OpenFGA, used to check/compute fine‑grained permissions (who can access what) this SDK will use the Auth0 FGA you will make available shortly.
* `dotenv`: Loads environment variables from a `.env` file so sensitive information doesn’t get hardcoded 
* `fastmcp`: High-level framework to build MCP servers quickly (routing, tool registration, server boilerplate).

Moreover, the `documentApi.ts` file has a mocked implementation of the document library. The implementation supports both public and private documents.

## Implementing the MCP Server in TypeScript

First, create a new file `tools.ts` inside the `src` folder:

```bash
touch src/tools.ts
```

Import the dependencies and instantiate the DocumentAPI class:

```typescript
import {FastMCP} from "fastmcp";
import {DocumentApi} from "./documentApi";

const documentApi = new DocumentApi();
```

Now, create a function to register the MCP Tools. In another section you’ll add the authorization to these.

```typescript
export function registerTools(mcpServer: FastMCP) {

}
```

Now, let's implement the **Who am I** tool, with limited capability as we don't have any information about the logged user yet.

```typescript
   mcpServer.addTool({
       name: "get_whoami",
       description: "Returns the information about the authenticated user",
       execute: async (_args) => {
           return {
               content: [
                   {
                       type: "text",
                       text: "I don't know who you are yet. But I will!"
                   }
               ]
           }
       }
   });
```

For the **Get Date and Time**, use the following code:

```typescript
   mcpServer.addTool({
       name: "get_datetime",
       description: "Returns the current UTC date and time",
       execute: async () => {
           const utcDateTime = new Date().toISOString();
           return {
               content: [
                   {
                       type: "text",
                       text: utcDateTime,
                   },
               ],
           };
       },
   });
```

Lastly, the **Get Documents** source code. For this version, only public documents are returned. We need FGA for private documents.

```typescript
   mcpServer.addTool({
       name: "get_documents",
       description:  "Retrieves important documents",
       execute: async (args) => {


           const canViewPrivate = false
           const documents = await documentApi.getDocuments(canViewPrivate);


           return {
               content: [
                   {
                       type: "text",
                       text: JSON.stringify(
                           {
                               count: documents.length,
                               filter: "public",
                               documents: documents,
                           }, null, 2)
                   },
               ],
           };
       },
   });
}
```

All tools are registered. Now, configure FastMCP and test it. Open the `index.ts` file and add the following code:

```typescript
import "dotenv/config";

import { FastMCP } from "fastmcp";
import { registerTools } from "./tools";

const PORT = parseInt(process.env.PORT ?? "3001", 10);

export const server = new FastMCP({
   name: "FastMCP Auth0 Example Server",
   version: "1.0.0",
});

const start = async () => {
   registerTools(server);

   try {
       /**
        * Starts the server.
        */
       await server.start({
           transportType: "httpStream",
           httpStream: {
               port: PORT,
               endpoint: "/mcp",
               stateless: true,
           },
       });
   } catch (err) {
       console.error(err);
       process.exit(1);
   }
};

start();
```

Change the `package.json` file to include the _start_ script that will start the server:

```typescript
"start": "tsx src/index.ts --project tsconfig.json"
```

To start the FastMCP server run the following command at the root folder of the project:

```bash
npm run start
```

You should see the following output:

```bash
> fastmpc-fga@1.0.0 start
> tsx src/index.ts --project tsconfig.json

[FastMCP info] Starting server in stateless mode on HTTP Stream at http://localhost:3001/mcp
```

An MCP server can be accessed through requests made by an MCP client. Since a browser is not an MCP client, in order to see results on a screen. You need to use a supported client or a debugging tool like the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).

## Testing Your MCP Server Using MCP Inspector

Start Inspector with the following command:
```bash
npx @modelcontextprotocol/inspector@latest
```

The output will provide a valid URL with the token to use in Inspector (eg.: `http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=ed3…6a3d9`). Open it in your browser and change the following fields:

* `Transport Type` to `Streamable HTTP`
* `URL` to `http://localhost:3001/mcp`

And click on **Connect**. You should see a similar screen:

![Inspector Screenshot](https://images.ctfassets.net/23aumh6u8s0i/370J28dBIZ0ZQcFJnVU6Dn/a949722ca3139f5919d16f3111c73687/01-mcp-inspector.png)

Now, click on the `List Tools` button to show all the tools available and ask to execute one of them.

![Inspector with tools listed](https://images.ctfassets.net/23aumh6u8s0i/6PX9NAZMUhhpGDANk6KVer/67c0edc47fde4a3eb687d6f2be152f50/02-run-tool-in-inspector.png)

Ok, everything is working! Now, let's add authorization.

**Note**: you can check your progress by changing the branch to `02-fastmcp`

## Setting Up MCP Server for Access Control

Now, you  can configure access control. Let’s start with  OAuth 2.1 to limit access to the tools.

First you need to adjust the code to add the proper authorization related classes and functions, then we need to configure your Auth0 tenant. In the end, we will show you how to extract an access token from the request and use it to check authorization.

First, create the file `src/types.ts` with the following content. This type will be used to pass authenticated information.

```typescript
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types";

export type FastMCPAuthSession = AuthInfo & {
 [key: string]: unknown;
};
```

Now, create the file `src/auth0.ts` with the following content:

```typescript
import type { IncomingMessage } from "http";
import { ApiClient, getToken } from "@auth0/auth0-api-js";
import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors";
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types";
import { FastMCPAuthSession } from "./types";

const AUTH0_DOMAIN = process.env.AUTH0_DOMAIN as string;
const AUTH0_AUDIENCE = process.env.AUTH0_AUDIENCE as string;

const apiClient = new ApiClient({
 domain: AUTH0_DOMAIN,
 audience: AUTH0_AUDIENCE,
});

function isNonEmptyString(value: unknown): value is string {
 return typeof value === "string" && value.length > 0;
}

export const authenticate = async (
 request: IncomingMessage,
): Promise<FastMCPAuthSession> => {
 try {
   const accessToken = getToken(request.headers);
   const decoded = await apiClient.verifyAccessToken({
     accessToken,
   });

   if (!isNonEmptyString(decoded.sub)) {
     throw new InvalidTokenError(
       "Token is missing required subject (sub) claim",
     );
   }

   let clientId: string | null = null;
   if (isNonEmptyString(decoded.client_id)) {
     clientId = decoded.client_id;
   } else if (isNonEmptyString(decoded.azp)) {
     clientId = decoded.azp;
   }

   if (!clientId) {
     throw new InvalidTokenError(
       "Token is missing required client identification (client_id or azp claim).",
     );
   }

   return {
     token: accessToken,
     clientId,
     scopes:
       typeof decoded.scope === "string"
         ? decoded.scope.split(" ").filter(Boolean)
         : [],
     ...(decoded.exp && { expiresAt: decoded.exp }),
     extra: {
       sub: decoded.sub,
       ...(isNonEmptyString(decoded.client_id) && {
         client_id: decoded.client_id,
       }),
       ...(isNonEmptyString(decoded.azp) && { azp: decoded.azp }),
       ...(isNonEmptyString(decoded.name) && { name: decoded.name }),
       ...(isNonEmptyString(decoded.email) && { email: decoded.email }),
     },
   } satisfies AuthInfo;
 } catch (error) {
   console.error(error);
   throw new Response(null, {
     status: 401,
     statusText: "Unauthorized",
   });
 }
};
```

The code above will do all the authentication logic. We use `ApiClient` from the Auth0 SDK to validate tokens, but we need to extract tokens from the request. By the end, we return a `FastMCPAuthSession` object that will be used by the MCP server to authenticate the user and add some extra information to identify the user.

Now, let's change the `index.ts` file to use the new authentication function. You need to change the beginning of the file indicating the authentication function to use (importing `authenticate` and `FastMCPAuthSession` types):

```typescript
import { FastMCP } from "fastmcp";
import { registerTools } from "./tools";
import { authenticate } from "./auth0";
import { FastMCPAuthSession } from "./types";

const PORT = parseInt(process.env.PORT ?? "3001", 10);

export const server = new FastMCP<FastMCPAuthSession>({
 name: "FastMCP Auth0 Example Server",
 version: "1.0.0",
 authenticate,
});
```

Now, we need to change the **Who am I** tool to use the authenticated user information:

```typescript
import { FastMCPAuthSession } from "./types";

export function registerTools(mcpServer: FastMCP<FastMCPAuthSession>) {
 mcpServer.addTool({
   name: "get_whoami",
   description: "Returns the information about the authenticated user",
   execute: async (_args, { session: authInfo }) => {
     const info = { user: authInfo?.extra, scopes: authInfo?.scopes };
     return {
       content: [
         {
           type: "text",
           text: JSON.stringify(info, null, 2),
         },
       ],
     };
   },
 })
```

To test it, you need to create an API in Auth0 and set up a store in Auth0 FGA, then you’ll be able to fill the `.env` file with the necessary variables. Let’s start with the API.

**Note**: you can check your progress by changing the branch to `03-oauth`.

## Creating an API in Auth0 to Represent Your MCP Server

To represent your MCP server within Auth0 you need to create an API. Go to [APIs in the Auth0 Dashboard](https://manage.auth0.com/#/apis). If you haven't an Auth0 account, you can [sign up for free here](https://a0.to/blog_signup). Then, go to the [APIs section](https://manage.auth0.com/#/apis) and click on *Create API*.

![Create an API](https://images.ctfassets.net/23aumh6u8s0i/6i7xMOUAB4xfgpsl0hdrf3/ec9f4c3e6c5d86e377109b33afa319d0/03-create-API.png)

This will open the form for configuring the API. Set the following fields:

- **Name**, a friendly name or description for the API. Enter **FastMCP Example** for this sample.
- **Identifier**, which is an identifier that the client application uses to request [access tokens](https://auth0.com/docs/secure/tokens/access-tokens) for the API. Enter the string `https://fastmcpexample.com`. This identifier is also known as **audience**.
- **Signing Algorithm**, leave the default setting, RS256.

After entering those values, click the *Create* button. You'll be taken to the API page. Navigate to the Settings tab check “Allow Offline Access” so we can generate Access Token’s in the format of a [JWT](https://auth0.com/docs/secure/tokens/json-web-tokens) for this API.

![Allow offline access](https://images.ctfassets.net/23aumh6u8s0i/3tvlS5kcOTgsJkApvC7eXM/1bbe41b323479b2c8cd7e34dcc0fce5a/04-allow-offline-access.png)

Create a file called `.env` at root folder and fill with the following values:

```bash
AUTH0_DOMAIN=<<YOUR AUTH0 DOMAIN>>
AUTH0_AUDIENCE=https://fastmcpexample.com
```

To get the domain, you can access any application and copy the domain value:

![The app details](https://images.ctfassets.net/23aumh6u8s0i/5HSAJisrk6Kn9MIsf7JKm7/375d0f8bc420abbc9d55e318c78b6b1f/05-app.png)

## Configuring Auth0 FGA

Now that you have your API setup, it’s time to configure your Auth0 FGA account and create the authorization model. Go to the [Auth0 FGA dashboard](https://dashboard.fga.dev/) and navigate the _Store Settings_ section, here you’ll need to grab the _API URL_ and _STORE ID_ and add it to the `.env` file:

```bash
FGA_API_URL='<<FGA API URL>>'
FGA_STORE_ID='<<FGA STORE ID>>'
```

Also, create a new client in the Authorized Clients section:

![Add authorized clients](https://images.ctfassets.net/23aumh6u8s0i/4EMSX5FMnRvO6TwRo1Ffh0/6a7bc3921d2df475091264936fd97c7c/06-authorized-clients-auth0-fga.png)

Give it a name and select all client permissions. In the next page, copy the  _Client ID_, _Client Secret_, _API Token Issuer_ and _API Audience_ values and update the `.env` file:

```bash
FGA_API_TOKEN_ISSUER='<< FGA API ISSUER >>'
FGA_API_AUDIENCE='<< FGA AUDIENCE >>'
FGA_CLIENT_ID='<< FGA CLIENT ID >>'
FGA_CLIENT_SECRET='<< FGA CLIENT SECRET >>'
```

Most of the changes in FGA model and tuples can be done using the web interface. For this post we recommend using [fga cli](https://github.com/openfga/cli) for flexibility and reproducibility. Follow the instructions for installation and **make sure all environment variables defined at this section are exported** before running any CLI command moving forward.

### Defining the FGA authorization model

The FGA authorization model defines the fine-grained permissions based on the relationship between objects. Create a file called `fga/model.fga` with the following content:

```yml
model
 schema 1.1

   type user

   type group
       relations
           define member : [user]

   type role
       relations
           define assignee : [user, group#member]

   type tool
       relations
           define can_use : [user:*, user, role#assignee,  user with temporal_access, role#assignee with temporal_access]
           define can_view_private_documents : [role#assignee]

   condition temporal_access(grant_time: timestamp, grant_duration: duration, current_time: timestamp) {
       current_time < grant_time + grant_duration
   }
```

In this model, we define a security schema revolving around the MCP server tools.

We have three main actors:
* **user**: The individual trying to access our functions.
* **group**: A collection of users (e.g., 'Admins').
* **role**: A specific job function (e.g., 'content_editor') assigned to users or groups.

The core logic lies in the `tool` type, which enforces two permissions:
1.  **`can_use`**: Determines who can execute the tool. It is flexible, allowing access to specific users, roles, or the public. It also supports **Temporal Access**, checking the `temporal_access` condition to automatically expire permissions after a set time.
2.  **`can_view_private_documents`**: Controls data visibility. This is restricted solely to users with this relationship.

Also, add the following set of tuples (create a file called `fga/tuples.yaml` with the following content):

```yaml
- user: user:*
 relation: can_use
 object: tool:get_datetime

- user: role:admin#assignee
 relation: can_use
 object: tool:get_whoami

- user: role:admin#assignee
 relation: can_use
 object: tool:get_documents

- user: role:admin#assignee
 relation: can_view_private_documents
 object: tool:get_documents

- user: role:content_editor#assignee
 relation: can_use
 object: tool:get_whoami

- user: role:content_editor#assignee
 relation: can_use
 object: tool:get_documents

- user: group:marketing#member
 relation: assignee
 object: role:content_editor

- user: group:managers#member
 relation: assignee
 object: role:admin
```

These tuples tell the story of our permissions:

1. **Public Access:** `tool:get_datetime` is available to everyone (`user:*`).
2. **Admin Power:** The `admin` role has full access. They can use `whoami` and `get_documents`, and crucially, they are the only ones granted `can_view_private_documents`.
3. **Editor Limits:** The `content_editor` role can use the tools (`whoami`, `get_documents`) but is *not* given permission to view private documents. They get functionality without sensitive data visibility.

To set the FGA model, you can use the FGA CLI you configured earlier. First, load the environment variables from the `.env` file:

```bash
set -a && source .env && set +a
```

Then, run the following command:

```bash
fga model write --file ./fga/model.fga
```

Now, we need to push the tuples to Auth0 FGA:

```bash
fga tuple write --file ./fga/tuples.yaml
```

Now that our model is set, we can change our tools to use FGA to check permissions.

**Note**: you can check your progress by changing the branch to `04-fga-model`

## Connecting FGA With Your MCP Server

Create a new file called `src/openfga.ts`, that will have all OpenFGA configuration and logic:

```typescript
import {
 OpenFgaClient,
 CredentialsMethod,
 ConsistencyPreference,
} from "@openfga/sdk";

const FGA_API_URL = process.env.FGA_API_URL as string;
const FGA_STORE_ID = process.env.FGA_STORE_ID as string;
const FGA_API_TOKEN_ISSUER = process.env.FGA_API_TOKEN_ISSUER as string;
const FGA_API_AUDIENCE = process.env.FGA_API_AUDIENCE as string;
const FGA_CLIENT_ID = process.env.FGA_CLIENT_ID as string;
const FGA_CLIENT_SECRET = process.env.FGA_CLIENT_SECRET as string;

let fgaClient = new OpenFgaClient({
 apiUrl: FGA_API_URL,
 storeId: FGA_STORE_ID,
 credentials: {
   method: CredentialsMethod.ClientCredentials,
   config: {
     apiTokenIssuer: FGA_API_TOKEN_ISSUER,
     apiAudience: FGA_API_AUDIENCE,
     clientId: FGA_CLIENT_ID,
     clientSecret: FGA_CLIENT_SECRET,
   },
 },
});

export async function getTools(userId: string): Promise<string[]> {
 const currentTime = new Date().toISOString();
 const toolNames = ["get_whoami", "get_datetime", "get_documents"];
 const checks = toolNames.map((toolName) => ({
   user: `user:${userId}`,
   relation: "can_use",
   object: `tool:${toolName}`,
   context: {
     current_time: currentTime,
   },
 }));
 const response = await fgaClient.batchCheck(
   { checks },
   {
     consistency: ConsistencyPreference.HigherConsistency,
   },
 );
 const allowedTools: string[] = [];
 response.result?.forEach((checkResult) => {
   if (checkResult.allowed) {
     const toolName = checkResult.request?.object?.replace("tool:", "");
     if (toolName) {
       allowedTools.push(toolName);
     }
   }
 });
 return allowedTools;
}

export async function checkPrivateDocumentsAccess(
 userId: string,
): Promise<boolean> {
 const currentTime = new Date().toISOString();
 const { allowed } = await fgaClient.check(
   {
     user: `user:${userId}`,
     relation: "can_view_private_documents",
     object: `tool:get_documents`,
     context: {
       current_time: currentTime,
     },
   },
   {
     consistency: ConsistencyPreference.HigherConsistency,
   },
 );
 return allowed ?? false;
}

export { fgaClient };
```

The code above will use the OpenFGA SDK to check permissions using the `can_use` relation, and `can_view_private_documents` to check if the user has access to private documents. As you can see, there are no roles at this point: the code checks for relations, FGA handles permissions and roles internally.

We need to change `FastMCPAuthSession` to include the `availableTools` field. Edit the `src/types.ts` file to include the following code:

```typescript
export type FastMCPAuthSession = AuthInfo & {
 availableTools?: string[];
 [key: string]: unknown;
};
```

Now, we need to change `src/auth0.ts` to use the `get_tools` function and fill the `FastMCPAuthSession` object:

```typescript
// ...
import { getTools } from "./openfga";
// ...

   let availableTools = await getTools(decoded.sub);

   return {
     token: accessToken,
     clientId,
     scopes:
       typeof decoded.scope === "string"
         ? decoded.scope.split(" ").filter(Boolean)
         : [],
     ...(decoded.exp && { expiresAt: decoded.exp }),
     availableTools,
     extra: {
       sub: decoded.sub,
       ...(isNonEmptyString(decoded.client_id) && {
         client_id: decoded.client_id,
       }),
       ...(isNonEmptyString(decoded.azp) && { azp: decoded.azp }),
       ...(isNonEmptyString(decoded.name) && { name: decoded.name }),
       ...(isNonEmptyString(decoded.email) && { email: decoded.email }),
     },
   } satisfies FastMCPAuthSession;

// ...
```

Finally, we need to change the `registerTools` function to use the `availableTools`. Add a new function called `hasAccess` that will receive the _tool name_ and tell if the user has access to it. Finally, apply the `hasAccess` function to the `canAccess` attribute:

```typescript
function hasAccess(toolName: string): (auth: FastMCPAuthSession) => boolean {
 return (auth: FastMCPAuthSession) => {
   return auth.availableTools?.includes(toolName) ?? false;
 };
}

// ...
   mcpServer.addTool({
       name: "get_whoami",
       description: "Returns the information about the authenticated user",
       canAccess: hasAccess("get_whoami"),
// ...

 mcpServer.addTool({
   name: "get_datetime",
   description: "Returns the current UTC date and time",
   canAccess: hasAccess("get_datetime"),

// ...
    
 mcpServer.addTool({
   name: "get_documents",
   description: "Retrieves important documents",
   canAccess: hasAccess("get_documents"),

// ...
```

To complete all the changes, the `get_documents` tool needs to check if the user has access to private documents:

```typescript
// ...
   execute: async (args, { session: authInfo }) => {
     const sub = (authInfo!.extra as { sub: string }).sub;
     const canViewPrivate = await checkPrivateDocumentsAccess(sub);
     const documents = await documentApi.getDocuments(canViewPrivate);
// ...
```

Before starting the server, the user needs to be added to a group or associated to a relation. Let's add your user to the
admin role.

First, execute the `get_whoami` to get the user subject identifier (`sub`). It should be similar to `auth0|6935e203a6322e4b948d256a`. 

![Result of whoami tool call](https://images.ctfassets.net/23aumh6u8s0i/9rz7rndYGaCegDE7OxqSC/eb9ae026bc1518f1828713d2850da0e9/07-tools-list.png)

Now, run the following command to add the user to the manager group:

```bash
fga tuple write --store-id "$FGA_STORE_ID" "user:${USER_ID}" member "group:managers" --on-duplicate ignore
```

Now, run the application again and check that you can access the tool and finally have access to private documents.

**Note**: you can check the final version in branch `05-fga`

## Configuring MCP Inspector Authentication

Now that your MCP server requires authentication and we have all the accounts set, we need to generate an access token to interact with the server. You can generate one by going to _Applications_ > _APIs_ section at Auth0 dashboard and then access the _Test_ tab. At the **_Response_** section, you’ll have a valid token that can be used. Copy all the value inside `access_token` attribute:

![Get the token from the Auth0 Dashboard](https://images.ctfassets.net/23aumh6u8s0i/36XOdOc7x39ihUpcDtYzME/77f2f7b0a3e12bfa7649856fcfa3bfce/08-token.png)

Now, in the Inspector web interface, open the _Authentication_ section and fill the Custom header Authorization with the token copied above (prepend `bearer ` before the token). After that, connect to your server again.

![Set the authorization details](https://images.ctfassets.net/23aumh6u8s0i/3uAyXRDKePhazVHtWJ4QCG/48f3c2750f7ed68de9bc643d78424684/09-authorization-in-inspector.png)

## Recap

In this tutorial, we've explored how to build a secure MCP server by combining FastMCP, Auth0, and Fine-Grained Authorization (FGA). You've learned how to:
* Bootstrap an MCP server using FastMCP and expose tools that LLMs can discover and invoke
* Integrate OAuth 2.1 with Auth0 to protect your endpoints
* Define a relationship-based authorization model with OpenFGA to control both tool access and data visibility
* Dynamically filter tools and content based on user permissions, creating a truly personalized experience for each user

By leveraging FGA's graph-based permission system, your MCP server can now make nuanced authorization decisions—like granting temporary access, role-based tool visibility, and document-level filtering—all without hardcoding business rules into your application logic.

Have questions or want to share what you've built? Join the conversation in our community forum. We'd love to see how you're using MCP and FGA together!