Commit
·
62ca1b3
1
Parent(s):
fa14553
authentication addeed
Browse files
app.py
CHANGED
|
@@ -96,8 +96,15 @@ if __name__ == "__main__":
|
|
| 96 |
try:
|
| 97 |
# Add web routes for landing page and API key generation
|
| 98 |
from starlette.responses import HTMLResponse, JSONResponse
|
|
|
|
| 99 |
from database.api_keys import generate_api_key as db_generate_api_key
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
@mcp.custom_route("/", methods=["GET"])
|
| 102 |
async def landing_page(request):
|
| 103 |
"""Landing page with MCP connection information"""
|
|
|
|
| 96 |
try:
|
| 97 |
# Add web routes for landing page and API key generation
|
| 98 |
from starlette.responses import HTMLResponse, JSONResponse
|
| 99 |
+
from starlette.requests import Request
|
| 100 |
from database.api_keys import generate_api_key as db_generate_api_key
|
| 101 |
|
| 102 |
+
# NOTE: Custom middleware for FastMCP has known limitations (GitHub issue #817)
|
| 103 |
+
# Headers/state set in middleware are NOT accessible inside FastMCP tools.
|
| 104 |
+
# For local development, use SKIP_AUTH=true with ENV=development in .env
|
| 105 |
+
# For production, API key validation happens via extract_api_key_from_request() in server.py
|
| 106 |
+
logger.info("[Auth] Using FastMCP built-in request handling for authentication")
|
| 107 |
+
|
| 108 |
@mcp.custom_route("/", methods=["GET"])
|
| 109 |
async def landing_page(request):
|
| 110 |
"""Landing page with MCP connection information"""
|
server.py
CHANGED
|
@@ -45,6 +45,25 @@ logger = logging.getLogger(__name__)
|
|
| 45 |
# Store API key per request using context variable
|
| 46 |
_current_api_key: ContextVar[str] = ContextVar('api_key', default=None)
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
def get_api_key_from_context() -> str:
|
| 49 |
"""
|
| 50 |
Get API key from the current request context.
|
|
@@ -56,15 +75,33 @@ def extract_api_key_from_request():
|
|
| 56 |
"""
|
| 57 |
Extract API key from HTTP request and store in context variable.
|
| 58 |
Called at the start of each tool execution.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
"""
|
| 60 |
try:
|
| 61 |
from fastmcp.server.dependencies import get_http_request
|
| 62 |
request = get_http_request()
|
|
|
|
|
|
|
| 63 |
api_key = request.query_params.get('api_key')
|
| 64 |
if api_key:
|
| 65 |
_current_api_key.set(api_key)
|
| 66 |
logger.debug(f"API key extracted from request: {api_key[:10]}...")
|
| 67 |
return api_key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
except RuntimeError:
|
| 69 |
# No HTTP request available (e.g., stdio transport)
|
| 70 |
logger.debug("No HTTP request available for API key extraction")
|
|
@@ -124,7 +161,9 @@ def get_authenticated_user():
|
|
| 124 |
|
| 125 |
# METHOD 3: Development bypass mode (local testing only)
|
| 126 |
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 127 |
-
|
|
|
|
|
|
|
| 128 |
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 129 |
|
| 130 |
if skip_auth:
|
|
|
|
| 45 |
# Store API key per request using context variable
|
| 46 |
_current_api_key: ContextVar[str] = ContextVar('api_key', default=None)
|
| 47 |
|
| 48 |
+
# Session-to-API-key store: maps session_id -> api_key
|
| 49 |
+
# This allows tool calls to authenticate using the API key from the SSE connection
|
| 50 |
+
_session_auth_store: dict[str, str] = {}
|
| 51 |
+
|
| 52 |
+
def store_session_api_key(session_id: str, api_key: str):
|
| 53 |
+
"""Store API key for a session (called when SSE connection is made)"""
|
| 54 |
+
_session_auth_store[session_id] = api_key
|
| 55 |
+
logger.debug(f"Stored API key for session {session_id[:16]}...")
|
| 56 |
+
|
| 57 |
+
def get_api_key_from_session(session_id: str) -> str:
|
| 58 |
+
"""Get API key from session store"""
|
| 59 |
+
return _session_auth_store.get(session_id)
|
| 60 |
+
|
| 61 |
+
def cleanup_session(session_id: str):
|
| 62 |
+
"""Remove session from store when disconnected"""
|
| 63 |
+
if session_id in _session_auth_store:
|
| 64 |
+
del _session_auth_store[session_id]
|
| 65 |
+
logger.debug(f"Cleaned up session {session_id[:16]}...")
|
| 66 |
+
|
| 67 |
def get_api_key_from_context() -> str:
|
| 68 |
"""
|
| 69 |
Get API key from the current request context.
|
|
|
|
| 75 |
"""
|
| 76 |
Extract API key from HTTP request and store in context variable.
|
| 77 |
Called at the start of each tool execution.
|
| 78 |
+
|
| 79 |
+
Checks multiple sources:
|
| 80 |
+
1. api_key query parameter in current request
|
| 81 |
+
2. session_id query parameter -> lookup in session store
|
| 82 |
"""
|
| 83 |
try:
|
| 84 |
from fastmcp.server.dependencies import get_http_request
|
| 85 |
request = get_http_request()
|
| 86 |
+
|
| 87 |
+
# METHOD 1: Direct api_key in query params
|
| 88 |
api_key = request.query_params.get('api_key')
|
| 89 |
if api_key:
|
| 90 |
_current_api_key.set(api_key)
|
| 91 |
logger.debug(f"API key extracted from request: {api_key[:10]}...")
|
| 92 |
return api_key
|
| 93 |
+
|
| 94 |
+
# METHOD 2: Look up by session_id (for MCP tool calls)
|
| 95 |
+
session_id = request.query_params.get('session_id')
|
| 96 |
+
if session_id:
|
| 97 |
+
api_key = get_api_key_from_session(session_id)
|
| 98 |
+
if api_key:
|
| 99 |
+
_current_api_key.set(api_key)
|
| 100 |
+
logger.debug(f"API key found via session {session_id[:16]}...")
|
| 101 |
+
return api_key
|
| 102 |
+
else:
|
| 103 |
+
logger.debug(f"No API key found for session {session_id[:16]}...")
|
| 104 |
+
|
| 105 |
except RuntimeError:
|
| 106 |
# No HTTP request available (e.g., stdio transport)
|
| 107 |
logger.debug("No HTTP request available for API key extraction")
|
|
|
|
| 161 |
|
| 162 |
# METHOD 3: Development bypass mode (local testing only)
|
| 163 |
# SECURITY: Only allow SKIP_AUTH in development environments
|
| 164 |
+
# Check both ENV and ENVIRONMENT variables for compatibility
|
| 165 |
+
env = os.getenv("ENV") or os.getenv("ENVIRONMENT", "production")
|
| 166 |
+
env = env.lower()
|
| 167 |
skip_auth = os.getenv("SKIP_AUTH", "false").lower() == "true"
|
| 168 |
|
| 169 |
if skip_auth:
|