Update asgi_app.py
This commit is contained in:
+45
-36
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user