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:
- Set up your Descope MCP Server in the Descope Console
- Get your
.well-knownURL 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
audclaim in access tokens and should match themcp_server_urlyou pass to this SDK - Discovery endpoints: Use the server's discovery settings to copy the MCP Server's
.well-knownOpenID configuration URL and pass it aswell_known_url
For detailed instructions, see MCP Server Settings.
Installation
To install the SDK, run the following command:
pip install descope-mcpThen 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 scopesvalidate_token_and_get_user_id()- Convenience function that validates a token and returns just the user IDrequire_scopes()- Validates that a token contains required scopes, raisingInsufficientScopeErrorif 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-knownendpoint - Token expiration
- Audience (
aud) claim matchesmcp_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 approacherror_description: Human-readable descriptionmissing_scopes: List of missing scopestoken_scopes: List of scopes in the tokenrequired_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
)from descope_mcp import get_connection_token
# Get latest token (any scopes)
token = get_connection_token(
user_id="user-123",
app_id="google-calendar",
access_token=mcp_access_token
)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)}