Guides and Tutorials/Model Context Protocol (MCP)

Python MCP SDK

The Descope Python MCP SDK (descope-mcp) provides a simple way to integrate Descope authentication and authorization with MCP (Model Context Protocol) servers built with Python.

Overview

The SDK provides:

  • Token validation with scope and audience enforcement
  • Connection token retrieval using MCP server access tokens (default) or management keys
  • Scope validation following the MCP spec for insufficient scope errors
  • Integration with FastMCP and the official MCP SDK

Compatibility

  • Python: 3.8+
  • MCP SDK: 1.0.0+
  • Descope SDK: 1.0.0+
  • FastMCP: 2.0+

The SDK can also be imported alongside the official Descope Python SDK without conflicts.

Prerequisites

Before using the SDK, you need to:

  1. Set up your Descope MCP Server in the Descope Console
  2. Get your .well-known URL from the MCP server settings

Setting Up Your MCP Server

In Descope, create or configure your MCP server in Agentic Identity Hub → MCP Servers → Settings. The settings include:

  • MCP Server URL: If set, this becomes the aud claim in access tokens and should match the mcp_server_url you pass to this SDK
  • Discovery endpoints: Use the server's discovery settings to copy the MCP Server's .well-known OpenID configuration URL and pass it as well_known_url

For detailed instructions, see MCP Server Settings.

Installation

To install the SDK, run the following command:

pip install descope-mcp

Then to initialize the SDK, use the following code:

from descope_mcp import DescopeMCP
 
# Initialize SDK with your MCP server configuration
DescopeMCP(
    well_known_url="https://api.descope.com/v1/apps/agentic/<Descope Project ID>/<Descope MCP Server URL>/.well-known/openid-configuration",
    mcp_server_url="https://your-mcp-server.com"  # Optional: for audience validation
)

Key Functions

The SDK provides the following main functions for working with MCP servers:

  • validate_token() - Validates MCP server access tokens and returns token claims including user ID and scopes
  • validate_token_and_get_user_id() - Convenience function that validates a token and returns just the user ID
  • require_scopes() - Validates that a token contains required scopes, raising InsufficientScopeError if scopes are missing (MCP spec-compliant)
  • get_connection_token() - Retrieves OAuth tokens for third-party services stored in Descope Connections, with support for specific scopes or latest token retrieval

Each function is detailed in the sections below.

Token Validation

Validate MCP server access tokens with signature verification, expiration checking, and audience validation:

from descope_mcp import validate_token, validate_token_and_get_user_id
 
# Get full validation result
result = validate_token(access_token)
user_id = result.get("sub") or result.get("userId")
scopes = result.get("scopes", [])
 
# Or get user ID directly
user_id = validate_token_and_get_user_id(access_token)

The validation checks:

  • Token signature against JWKs from the .well-known endpoint
  • Token expiration
  • Audience (aud) claim matches mcp_server_url (if provided)
  • Token issuer matches the configured Descope project

Scope Validation

The SDK provides scope validation that follows the MCP spec's Runtime Insufficient Scope Errors.

Using require_scopes()

from descope_mcp import validate_token, require_scopes, InsufficientScopeError
 
@mcp.tool()
def my_tool(mcp_access_token: str) -> str:
    try:
        token_result = validate_token(mcp_access_token)
        require_scopes(token_result, ["read", "write"])
        return "Success"
    except InsufficientScopeError as e:
        # Returns MCP spec-compliant error response
        return e.to_json()

InsufficientScopeError

When require_scopes() detects missing scopes, it raises an InsufficientScopeError that follows the MCP spec:

try:
    require_scopes(token_result, ["calendar.read"])
except InsufficientScopeError as e:
    missing = e.missing_scopes  # ["calendar.read"]
    combined = e.combined_scopes  # ["read", "write", "calendar.read"]
    scope_param = e.scope_parameter  # "read write calendar.read"
    
    # Get MCP spec-compliant JSON response
    error_json = e.to_json()

The error includes:

  • error: "insufficient_scope"
  • scope: Space-separated list of all scopes (existing + required) - uses recommended approach
  • error_description: Human-readable description
  • missing_scopes: List of missing scopes
  • token_scopes: List of scopes in the token
  • required_scopes: List of required scopes

Connection Tokens

Retrieve OAuth tokens for third-party services stored in Descope Connections:

from descope_mcp import get_connection_token
 
# Get token with specific scopes (uses access token by default)
token = get_connection_token(
    user_id="user-123",
    app_id="google-calendar",
    scopes=["https://www.googleapis.com/auth/calendar.readonly"],
    access_token=mcp_access_token  # Enables policy enforcement
)

The SDK uses MCP server access tokens by default for policy enforcement. Management keys can be used as a fallback for tenant-level tokens or when access tokens aren't available.

When using access_token, Descope enforces your access control policies before returning connection tokens. This ensures only authorized clients can retrieve tokens for specific connections.

Examples

You can find examples of MCP servers using our SDK in our AI repository in GitHub.

Here's a complete example of the SDK in action, showing token validation, scope checking, and connection token retrieval:

from mcp.server import FastMCP
from descope_mcp import DescopeMCP, validate_token, require_scopes, get_connection_token, InsufficientScopeError
import requests
 
# Initialize SDK
DescopeMCP(
    well_known_url="https://api.descope.com/v1/apps/agentic/<Descope Project ID>/<Descope MCP Server URL>/.well-known/openid-configuration",
    mcp_server_url="https://your-mcp-server.com"
)
 
mcp = FastMCP("calendar-server")
 
@mcp.tool()
async def get_calendar_events(
    max_results: int = 5,
    mcp_access_token: str = None
) -> str:
    """
    Get upcoming calendar events for the authenticated user.
    Requires 'calendar.read' scope.
    """
    if not mcp_access_token:
        return {"error": "Authentication required"}
    
    try:
        # Validate token
        token_result = validate_token(mcp_access_token)
        
        # Check required scopes
        require_scopes(token_result, ["calendar.read"])
        
        # Get user ID
        user_id = token_result.get("sub") or token_result.get("userId")
        
        # Get Google Calendar connection token
        google_token = get_connection_token(
            user_id=user_id,
            app_id="google-calendar",
            scopes=["https://www.googleapis.com/auth/calendar.readonly"],
            access_token=mcp_access_token
        )
        
        # Use token to call Google Calendar API
        from datetime import datetime
        now = datetime.utcnow().isoformat() + "Z"
        url = f"https://www.googleapis.com/calendar/v3/calendars/primary/events"
        params = {
            "timeMin": now,
            "maxResults": max_results,
            "orderBy": "startTime",
            "singleEvents": True
        }
        
        response = requests.get(
            url,
            headers={"Authorization": f"Bearer {google_token}"},
            params=params
        )
        
        if response.status_code != 200:
            return {"error": f"Google API error: {response.status_code}"}
        
        return response.json()
        
    except InsufficientScopeError as e:
        # Return MCP spec-compliant error
        return e.to_json()
    except Exception as e:
        return {"error": str(e)}
Was this helpful?

On this page