Skip to content

Commit af2d985

Browse files
committed
add config.py
1 parent 534f05d commit af2d985

4 files changed

Lines changed: 48 additions & 14 deletions

File tree

examples/example-fastmcp-mcp/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ AUTH0_DOMAIN=your-tenant.auth0.com
33
AUTH0_AUDIENCE=https://api.example.com
44
MCP_SERVER_URL=http://localhost:3001
55
PORT=3001
6-
DEBUG=false
6+
DEBUG=false
7+
CORS_ORIGINS=*

examples/example-fastmcp-mcp/src/auth0/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from __future__ import annotations
99

1010
import logging
11-
import os
1211
from collections.abc import Callable
1312

1413
from mcp.server.auth.routes import create_protected_resource_routes
@@ -25,10 +24,11 @@
2524

2625

2726
class Auth0Mcp:
28-
def __init__(self, name: str, audience: str, domain: str):
27+
def __init__(self, name: str, audience: str, domain: str, mcp_server_url: str | None = None):
2928
self.name = name
3029
self.audience = audience
3130
self.domain = domain
31+
self.mcp_server_url = mcp_server_url
3232
if not self.audience or not self.domain:
3333
raise RuntimeError("audience and domain must be provided")
3434
self.mcp = FastMCP(
@@ -113,9 +113,8 @@ def _build_www_authenticate_header(self, error_code: str, description: str, incl
113113
Build WWW-Authenticate header according to RFC 9728 Section 5.1.
114114
"""
115115
www_auth_params = [f'error="{error_code}"', f'error_description="{description}"']
116-
metadata_url = os.getenv('MCP_SERVER_URL')
117-
if include_resource_metadata and metadata_url:
118-
metadata_url = metadata_url.rstrip("/") + "/.well-known/oauth-protected-resource"
116+
if include_resource_metadata and self.mcp_server_url:
117+
metadata_url = self.mcp_server_url.rstrip("/") + "/.well-known/oauth-protected-resource"
119118
www_auth_params.append(f'resource_metadata="{metadata_url}"')
120119

121120
return f"Bearer {', '.join(www_auth_params)}"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from __future__ import annotations
2+
3+
import os
4+
from dataclasses import dataclass, field
5+
6+
from dotenv import load_dotenv
7+
8+
9+
@dataclass(frozen=True)
10+
class Config:
11+
auth0_domain: str
12+
auth0_audience: str
13+
mcp_server_url: str
14+
port: int = 3001
15+
debug: bool = True
16+
cors_origins: list[str] = field(default_factory=lambda: ["*"])
17+
18+
@classmethod
19+
def from_env(cls) -> Config:
20+
return cls(
21+
auth0_domain=os.environ["AUTH0_DOMAIN"],
22+
auth0_audience=os.environ["AUTH0_AUDIENCE"],
23+
mcp_server_url=os.getenv("MCP_SERVER_URL", "http://localhost:3001"),
24+
port=int(os.getenv("PORT", "3001")),
25+
debug=os.getenv("DEBUG", "false").lower() == "true",
26+
cors_origins=os.getenv("CORS_ORIGINS", "*").split(","),
27+
)
28+
29+
30+
def get_config() -> Config:
31+
"""Get application configuration."""
32+
load_dotenv()
33+
return Config.from_env()

examples/example-fastmcp-mcp/src/server.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@
22

33
import contextlib
44
import logging
5-
import os
65
from collections.abc import AsyncIterator
76

8-
from dotenv import load_dotenv
97
from starlette.applications import Starlette
108
from starlette.middleware.cors import CORSMiddleware
119
from starlette.routing import Mount
1210

1311
from .auth0 import Auth0Mcp
12+
from .config import get_config
1413
from .tools import register_tools
1514

16-
load_dotenv()
15+
config = get_config()
1716

1817
# Configure logging
1918
logging.basicConfig(level=logging.INFO)
2019
logger = logging.getLogger(__name__)
2120

2221
auth0_mcp = Auth0Mcp(
2322
name="Example FastMCP Server",
24-
audience=os.getenv("AUTH0_AUDIENCE"),
25-
domain=os.getenv("AUTH0_DOMAIN")
23+
audience=config.auth0_audience,
24+
domain=config.auth0_domain,
25+
mcp_server_url=config.mcp_server_url
2626
)
2727
register_tools(auth0_mcp)
2828

@@ -33,7 +33,7 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]:
3333
yield
3434

3535
starlette_app = Starlette(
36-
debug=os.getenv("DEBUG", "true").lower() == "true",
36+
debug=config.debug,
3737
routes=[
3838
# Add discovery metadata route
3939
*auth0_mcp.auth_metadata_router().routes,
@@ -51,13 +51,14 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]:
5151

5252
# Wrap ASGI application with CORS middleware to expose Mcp-Session-Id header
5353
# for browser-based clients (ensures 500 errors get proper CORS headers)
54+
5455
app = CORSMiddleware(
5556
starlette_app,
56-
allow_origins=["*"], # Adjust as needed for production
57+
allow_origins=config.cors_origins,
5758
allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods
5859
expose_headers=["Mcp-Session-Id"],
5960
)
6061

6162
if __name__ == "__main__":
6263
import uvicorn
63-
uvicorn.run(app, port=int(os.getenv("PORT", "3001")))
64+
uvicorn.run(app, port=config.port)

0 commit comments

Comments
 (0)