Update asgi_app.py

This commit is contained in:
saidsurucu
2025-07-09 19:44:31 +03:00
parent e65b42abc8
commit e8b92e347c
+45 -36
View File
@@ -48,10 +48,8 @@ custom_middleware = [
), ),
] ]
# Create MCP Starlette sub-application with authentication # Create MCP Starlette sub-application (without auth wrapper)
from mcp_auth_factory import create_app mcp_app = mcp_server.http_app(
mcp_app_with_auth = create_app()
mcp_app = mcp_app_with_auth.http_app(
path="/", path="/",
middleware=custom_middleware middleware=custom_middleware
) )
@@ -82,15 +80,15 @@ app = FastAPI(
description="MCP server for Turkish legal databases with OAuth authentication", description="MCP server for Turkish legal databases with OAuth authentication",
version="0.1.0", version="0.1.0",
middleware=custom_middleware, middleware=custom_middleware,
lifespan=mcp_app.lifespan, # Authentication-enabled MCP app lifespan lifespan=mcp_app.lifespan, # MCP app lifespan
default_response_class=UTF8JSONResponse # Use UTF-8 JSON encoder default_response_class=UTF8JSONResponse # Use UTF-8 JSON encoder
) )
# Add Stripe webhook router to FastAPI # Add Stripe webhook router to FastAPI
app.include_router(stripe_router, prefix="/api") app.include_router(stripe_router, prefix="/api")
# MCP Auth HTTP adapter now handled by create_app() - no need for separate router # Add MCP Auth HTTP adapter to FastAPI (handles OAuth endpoints)
# app.include_router(mcp_auth_router, prefix="/mcp") # Commented out to avoid duplication app.include_router(mcp_auth_router)
# Custom 401 exception handler for MCP spec compliance # Custom 401 exception handler for MCP spec compliance
@app.exception_handler(401) @app.exception_handler(401)
@@ -109,7 +107,8 @@ async def custom_401_handler(request: Request, exc: HTTPException):
return response return response
# MCP app handled via custom route handlers - mounting removed # Mount MCP app as sub-application at /mcp-server to avoid path conflicts
app.mount("/mcp-server", mcp_app)
# Add custom route to handle /mcp requests and forward to mounted app # Add custom route to handle /mcp requests and forward to mounted app
@app.api_route("/mcp", methods=["POST", "DELETE", "OPTIONS"]) @app.api_route("/mcp", methods=["POST", "DELETE", "OPTIONS"])
@@ -128,46 +127,33 @@ async def mcp_protocol_handler(request: Request):
content="Session terminated successfully" content="Session terminated successfully"
) )
# Optional: Validate Bearer JWT tokens as secondary auth method # Optional: Validate Bearer JWT tokens for direct API access
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "): if auth_header and auth_header.startswith("Bearer "):
token = auth_header.split(" ")[1] token = auth_header.split(" ")[1]
try: try:
# Validate Clerk JWT token using correct imports # Validate Clerk JWT token (simplified validation)
from clerk_backend_api.security.verifytoken import verify_token from clerk_backend_api import Clerk
from clerk_backend_api.security.types import VerifyTokenOptions clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY"))
jwt_claims = clerk.jwt_templates.verify_token(token)
# Create verification options
options = VerifyTokenOptions(
secret_key=os.getenv("CLERK_SECRET_KEY"),
api_url="https://api.clerk.com",
api_version="v1",
audience=None # Skip audience validation to avoid issues
)
# Validate Clerk JWT token directly
jwt_claims = verify_token(token, options)
user_id = jwt_claims.get("sub") user_id = jwt_claims.get("sub")
if user_id: if user_id:
logger.info(f"JWT Bearer token validated successfully for user: {user_id}") logger.info(f"Bearer JWT token validated for user: {user_id}")
# Add user info to request state # Add user info to request state
request.state.user_id = user_id request.state.user_id = user_id
request.state.auth_method = "bearer_jwt" request.state.token_scopes = jwt_claims.get("scopes", ["read", "search"])
else:
logger.warning("JWT Bearer token validation failed - no user_id in claims")
except Exception as e: except Exception as e:
logger.warning(f"JWT Bearer token validation failed: {str(e)}") logger.warning(f"Bearer token validation failed: {str(e)}")
# Don't fail here - let MCP Auth Toolkit handle it as fallback # Don't fail here - let MCP Auth Toolkit handle it
pass pass
# Forward the request to the MCP app # Forward the request to the mounted MCP app
async def receive(): async def receive():
return await request.receive() return await request.receive()
# Create new scope for the MCP app # Create new scope for the mounted app
scope = request.scope.copy() scope = request.scope.copy()
scope["path"] = "/" # Root path for MCP app scope["path"] = "/" # Root path for mounted app
scope["path_info"] = "/" scope["path_info"] = "/"
# Capture the response # Capture the response
@@ -180,7 +166,7 @@ async def mcp_protocol_handler(request: Request):
elif message["type"] == "http.response.body": elif message["type"] == "http.response.body":
response_parts["body"] += message.get("body", b"") response_parts["body"] += message.get("body", b"")
# Call the MCP app directly # Call the mounted MCP app
await mcp_app(scope, receive, send) await mcp_app(scope, receive, send)
# Return the response # Return the response
@@ -254,9 +240,32 @@ async def root():
}) })
# OAuth 2.0 Authorization Server Metadata proxy (for MCP clients that can't reach Clerk directly) # OAuth 2.0 Authorization Server Metadata proxy (for MCP clients that can't reach Clerk directly)
@app.get("/.well-known/oauth-authorization-server") # MCP Auth Toolkit expects this to be under /mcp/.well-known/oauth-authorization-server
@app.get("/mcp/.well-known/oauth-authorization-server")
async def oauth_authorization_server(): async def oauth_authorization_server():
"""OAuth 2.0 Authorization Server Metadata proxy to Clerk""" """OAuth 2.0 Authorization Server Metadata proxy to Clerk - MCP Auth Toolkit standard location"""
return JSONResponse({
"issuer": CLERK_ISSUER,
"authorization_endpoint": f"{BASE_URL}/auth/login",
"token_endpoint": f"{BASE_URL}/auth/callback",
"jwks_uri": f"{CLERK_ISSUER}/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
"scopes_supported": ["read", "search", "openid", "profile", "email"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "name"],
"code_challenge_methods_supported": ["S256"],
"service_documentation": f"{BASE_URL}/mcp",
"registration_endpoint": f"{BASE_URL}/auth/register",
"resource_documentation": f"{BASE_URL}/mcp"
})
# Keep root level for compatibility with some MCP clients
@app.get("/.well-known/oauth-authorization-server")
async def oauth_authorization_server_root():
"""OAuth 2.0 Authorization Server Metadata proxy to Clerk - root level for compatibility"""
return JSONResponse({ return JSONResponse({
"issuer": CLERK_ISSUER, "issuer": CLERK_ISSUER,
"authorization_endpoint": f"{BASE_URL}/auth/login", "authorization_endpoint": f"{BASE_URL}/auth/login",