Back to Fastmcp

Descope 🤝 FastMCP

docs/integrations/descope.mdx

4.0.0a14.8 KB
Original Source

import { VersionBadge } from "/snippets/version-badge.mdx";

<VersionBadge version="2.12.4" />

This guide shows you how to secure your FastMCP server using Descope, a complete authentication and user management solution. This integration uses the Remote OAuth pattern, where Descope handles user login and your FastMCP server validates the tokens.

Configuration

Prerequisites

Before you begin, you will need:

  1. To sign up for a Free Forever Descope account
  2. Your FastMCP server's URL (can be localhost for development, e.g., http://localhost:8000)

Step 1: Configure Descope

<Steps> <Step title="Configure a Descope application"> You can use either a resource-specific Descope MCP Server or a project-level inbound app.
To create an MCP Server, go to the [MCP Servers page](https://app.descope.com/mcp-servers) of the Descope Console, create a server, and enable **Dynamic Client Registration (DCR)**.


<Warning>
DCR is required for FastMCP clients to automatically register with your authentication server.
</Warning>
</Step> <Step title="Copy the Well-Known URL"> `DescopeProvider` accepts both resource-specific MCP Server URLs:
```
https://api.descope.com/v1/apps/agentic/P.../M.../.well-known/openid-configuration
```

and project-level inbound app URLs:

```
https://api.descope.com/v1/apps/P.../.well-known/openid-configuration
```
</Step> </Steps>

Step 2: Environment Setup

Create a .env file with your Descope configuration:

bash
DESCOPE_CONFIG_URL=https://api.descope.com/v1/apps/P.../.well-known/openid-configuration
BASE_URL=http://localhost:8000

Step 3: FastMCP Configuration

Create your FastMCP server file and use the DescopeProvider to handle all the OAuth integration automatically. Nothing reads .env automatically, so load it explicitly with python-dotenv (pip install python-dotenv) before constructing the provider — otherwise the values you just wrote stay invisible to os.environ.

python
import os

from dotenv import load_dotenv
from fastmcp import FastMCP
from fastmcp.server.auth.providers.descope import DescopeProvider

load_dotenv()

# DescopeProvider accepts either supported Well-Known URL format.
auth_provider = DescopeProvider(
    config_url=os.environ["DESCOPE_CONFIG_URL"],
    base_url=os.environ.get("BASE_URL", "http://localhost:8000"),
)

# Create FastMCP server with auth
mcp = FastMCP(name="My Descope Protected Server", auth=auth_provider)

Scope discovery and validation

When both scopes_supported and required_scopes are omitted, DescopeProvider discovers scopes_supported lazily from the OpenID configuration and advertises them to MCP clients. Provider construction remains network-free, and a transient discovery failure is retried on a later metadata request.

Set both options when clients should request a broader set of scopes than the server requires on every token:

python
from fastmcp.server.auth.providers.descope import DescopeProvider

auth_provider = DescopeProvider(
    config_url="https://api.descope.com/v1/apps/P.../.well-known/openid-configuration",
    base_url="https://your-fastmcp-server.com",
    scopes_supported=["mcp:read", "mcp:write"],
    required_scopes=["mcp:read"],
)

scopes_supported controls what the protected resource metadata advertises. required_scopes controls what the JWT verifier requires during token validation. When only required_scopes is set, those scopes are also advertised to clients.

Testing

To test your server, you can use the fastmcp CLI to run it locally. Assuming you've saved the above code to server.py (after replacing the environment variables with your actual values!), you can run the following command:

bash
fastmcp run server.py --transport http --port 8000

Now, you can use a FastMCP client to test that you can reach your server after authenticating:

python
from fastmcp import Client
import asyncio

async def main():
    async with Client("http://localhost:8000/mcp", auth="oauth") as client:
        tools = await client.list_tools()
        print(f"Authenticated. Server exposes {len(tools)} tools.")

if __name__ == "__main__":
    asyncio.run(main())

Production Configuration

For production deployments, load configuration from environment variables:

python
import os
from fastmcp import FastMCP
from fastmcp.server.auth.providers.descope import DescopeProvider

# Load configuration from environment variables
auth = DescopeProvider(
    config_url=os.environ.get("DESCOPE_CONFIG_URL"),
    base_url=os.environ.get("BASE_URL", "https://your-server.com")
)

mcp = FastMCP(name="My Descope Protected Server", auth=auth)