Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Auth0 Configuration (for 09_auth_example_auth0.py)
# Copy this file to .env and fill in your Auth0 credentials

# Your Auth0 tenant domain (e.g., "your-tenant.auth0.com")
AUTH0_DOMAIN=your-tenant.auth0.com

# The audience/identifier of your API (e.g., "https://your-tenant.auth0.com/api/v2/")
AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/

# Your Auth0 application client ID
AUTH0_CLIENT_ID=your-client-id

# Your Auth0 application client secret
AUTH0_CLIENT_SECRET=your-client-secret

# How to get these credentials:
# 1. Go to Auth0 Dashboard: https://manage.auth0.com/
# 2. Create an Application (Machine to Machine Application)
# 3. Create an API and note the identifier (audience)
# 4. Copy the credentials from the Application settings
7 changes: 7 additions & 0 deletions examples/01_basic_usage_example.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
7 changes: 7 additions & 0 deletions examples/02_full_schema_description_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
This example shows how to describe the full response schema instead of just a response example.
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
7 changes: 7 additions & 0 deletions examples/03_custom_exposed_endpoints_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
- When combining filters, a greedy approach will be taken. Endpoints matching either criteria will be included
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
7 changes: 7 additions & 0 deletions examples/04_separate_server_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
You can create an MCP server from one FastAPI app, and mount it to a different app.
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from fastapi import FastAPI

from examples.shared.apps.items import app
Expand Down
7 changes: 7 additions & 0 deletions examples/05_reregister_tools_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
This example shows how to re-register tools if you add endpoints after the MCP server was created.
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
7 changes: 7 additions & 0 deletions examples/06_custom_mcp_router_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
This example shows how to mount the MCP server to a specific APIRouter, giving a custom mount path.
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
7 changes: 7 additions & 0 deletions examples/07_configure_http_timeout_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
In case you have API endpoints that take longer than 5 seconds to respond, you can increase the timeout.
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
7 changes: 7 additions & 0 deletions examples/08_auth_example_token_passthrough.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
```
"""

import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from examples.shared.apps.items import app # The FastAPI app
from examples.shared.setup import setup_logging

Expand Down
116 changes: 86 additions & 30 deletions examples/09_auth_example_auth0.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import sys
from pathlib import Path

project_root = Path(__file__).parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

from fastapi import FastAPI, Depends, HTTPException, Request, status
from pydantic_settings import BaseSettings
from typing import Any
from typing import Any, Optional
import logging
import uuid

from fastapi_mcp import FastApiMCP, AuthConfig

from examples.shared.auth import fetch_jwks_public_key
from examples.shared.setup import setup_logging


Expand All @@ -15,42 +22,86 @@

class Settings(BaseSettings):
"""
For this to work, you need an .env file in the root of the project with the following variables:
For full Auth0 integration, you need an .env file in the root of the project with the following variables:
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret

Without Auth0 configuration, this example will run in DEMO mode with mock authentication.
"""

auth0_domain: str # Auth0 domain, e.g. "your-tenant.auth0.com"
auth0_audience: str # Audience, e.g. "https://your-tenant.auth0.com/api/v2/"
auth0_client_id: str
auth0_client_secret: str
auth0_domain: Optional[str] = None
auth0_audience: Optional[str] = None
auth0_client_id: Optional[str] = None
auth0_client_secret: Optional[str] = None

@property
def auth0_jwks_url(self):
if not self.auth0_domain:
return None
return f"https://{self.auth0_domain}/.well-known/jwks.json"

@property
def auth0_oauth_metadata_url(self):
if not self.auth0_domain:
return None
return f"https://{self.auth0_domain}/.well-known/openid-configuration"

@property
def is_demo_mode(self):
return self.auth0_domain is None or self.auth0_audience is None

class Config:
env_file = ".env"


settings = Settings() # type: ignore

DEMO_MODE = settings.is_demo_mode

if DEMO_MODE:
logger.warning("=" * 60)
logger.warning("Running in DEMO MODE (no Auth0 configuration found)")
logger.warning("To enable real Auth0 integration, create a .env file with:")
logger.warning(" AUTH0_DOMAIN=your-tenant.auth0.com")
logger.warning(" AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/")
logger.warning(" AUTH0_CLIENT_ID=your-client-id")
logger.warning(" AUTH0_CLIENT_SECRET=your-client-secret")
logger.warning("=" * 60)


async def lifespan(app: FastAPI):
app.state.jwks_public_key = await fetch_jwks_public_key(settings.auth0_jwks_url)
logger.info(f"Auth0 client ID in settings: {settings.auth0_client_id}")
logger.info(f"Auth0 domain in settings: {settings.auth0_domain}")
logger.info(f"Auth0 audience in settings: {settings.auth0_audience}")
if not DEMO_MODE:
from examples.shared.auth import fetch_jwks_public_key
app.state.jwks_public_key = await fetch_jwks_public_key(settings.auth0_jwks_url)
logger.info(f"Auth0 client ID in settings: {settings.auth0_client_id}")
logger.info(f"Auth0 domain in settings: {settings.auth0_domain}")
logger.info(f"Auth0 audience in settings: {settings.auth0_audience}")
else:
logger.info("Demo mode: using mock authentication")
yield


async def verify_auth(request: Request) -> dict[str, Any]:
if DEMO_MODE:
auth_header = request.headers.get("authorization", "")
if auth_header.startswith("Bearer "):
token = auth_header.split(" ")[1]
return {
"sub": f"demo_user_{uuid.uuid4().hex[:8]}",
"iss": "demo_mode",
"aud": "demo_audience",
"demo": True,
}

return {
"sub": f"demo_user_{uuid.uuid4().hex[:8]}",
"iss": "demo_mode",
"aud": "demo_audience",
"demo": True,
}

try:
import jwt

Expand All @@ -62,18 +113,16 @@ async def verify_auth(request: Request) -> dict[str, Any]:

header = jwt.get_unverified_header(token)

# Check if this is a JWE token (encrypted token)
if header.get("alg") == "dir" and header.get("enc") == "A256GCM":
raise ValueError(
"Token is encrypted, offline validation not possible. "
"This is usually due to not specifying the audience when requesting the token."
)

# Otherwise, it's a JWT, we can validate it offline
if header.get("alg") in ["RS256", "HS256"]:
claims = jwt.decode(
token,
app.state.jwks_public_key,
request.app.state.jwks_public_key,
algorithms=["RS256", "HS256"],
audience=settings.auth0_audience,
issuer=f"https://{settings.auth0_domain}/",
Expand Down Expand Up @@ -110,22 +159,29 @@ async def protected(user_id: str = Depends(get_current_user_id)):
return {"message": f"Hello, {user_id}!", "user_id": user_id}


# Set up FastAPI-MCP with Auth0 auth
mcp = FastApiMCP(
app,
name="MCP With Auth0",
description="Example of FastAPI-MCP with Auth0 authentication",
auth_config=AuthConfig(
issuer=f"https://{settings.auth0_domain}/",
authorize_url=f"https://{settings.auth0_domain}/authorize",
oauth_metadata_url=settings.auth0_oauth_metadata_url,
audience=settings.auth0_audience,
client_id=settings.auth0_client_id,
client_secret=settings.auth0_client_secret,
dependencies=[Depends(verify_auth)],
setup_proxies=True,
),
)
# Set up FastAPI-MCP
if DEMO_MODE:
mcp = FastApiMCP(
app,
name="MCP Demo (Auth0 Example)",
description="Demo mode - FastAPI-MCP with mock authentication",
)
else:
mcp = FastApiMCP(
app,
name="MCP With Auth0",
description="Example of FastAPI-MCP with Auth0 authentication",
auth_config=AuthConfig(
issuer=f"https://{settings.auth0_domain}/",
authorize_url=f"https://{settings.auth0_domain}/authorize",
oauth_metadata_url=settings.auth0_oauth_metadata_url,
audience=settings.auth0_audience,
client_id=settings.auth0_client_id,
client_secret=settings.auth0_client_secret,
dependencies=[Depends(verify_auth)],
setup_proxies=True,
),
)

# Mount the MCP server
mcp.mount_http()
Expand Down
2 changes: 1 addition & 1 deletion examples/shared/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging
import httpx

from examples.shared.setup import setup_logging
from .setup import setup_logging

setup_logging()

Expand Down
37 changes: 36 additions & 1 deletion fastapi_mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,46 @@
__version__ = "0.0.0.dev0" # pragma: no cover

from .server import FastApiMCP
from .types import AuthConfig, OAuthMetadata
from .types import (
AuthConfig,
OAuthMetadata,
ExtendedAuthConfig,
PanelConfig,
RateLimitConfig,
SecurityConfig,
UserRole,
Permission,
User,
UserCreate,
UserUpdate,
ApiKeyStatus,
ApiKey,
ApiKeyCreate,
ApiKeyUpdate,
CallStatus,
CallLog,
MonitorData,
)


__all__ = [
"FastApiMCP",
"AuthConfig",
"OAuthMetadata",
"ExtendedAuthConfig",
"PanelConfig",
"RateLimitConfig",
"SecurityConfig",
"UserRole",
"Permission",
"User",
"UserCreate",
"UserUpdate",
"ApiKeyStatus",
"ApiKey",
"ApiKeyCreate",
"ApiKeyUpdate",
"CallStatus",
"CallLog",
"MonitorData",
]
Loading