Merge branch 'upstream/dev' into fix/pr-6742-email-export

This commit is contained in:
Alexandre Teixeira
2026-10-20 15:01:55 +01:00
65 changed files with 2239 additions and 662 deletions
+41 -7
View File
@@ -114,6 +114,18 @@ def _mcp_oauth_redirect_uri() -> str:
return REDIRECT_URI
def _is_legacy_google_flow(identifier: str) -> bool:
"""Determine whether an identifier is the server_id of a configured Google OAuth MCP server."""
if not identifier:
return False
db = SessionLocal()
try:
srv = db.query(McpServer).filter(McpServer.id == identifier).first()
return bool(srv and srv.oauth_config)
finally:
db.close()
def setup_mcp_routes(mcp_manager: McpManager):
"""Setup MCP routes with the provided manager."""
@@ -487,10 +499,14 @@ def setup_mcp_routes(mcp_manager: McpManager):
db.close()
@router.get("/oauth/callback")
async def oauth_callback(code: str, state: str, request: Request):
async def oauth_callback(code: str = "", state: str = "", request: Request = None):
"""Handle OAuth callback. Generic MCP OAuth flows resolve via the
pending-state registry; Google flows fall through to the legacy path."""
require_admin(request)
if not state:
return HTMLResponse(_oauth_result_page("Error", "No OAuth state parameter found in the callback URL."), status_code=400)
if not code:
return HTMLResponse(_oauth_result_page("Error", "No authorization code found in the URL. Make sure you copied the full URL from your browser."), status_code=400)
from src.mcp_oauth import resolve_pending
if resolve_pending(state, code):
return HTMLResponse(_oauth_result_page(
@@ -498,8 +514,16 @@ def setup_mcp_routes(mcp_manager: McpManager):
"The MCP server is connecting. You can close this window and return to Odysseus.",
success=True,
))
# Legacy Google path: state is the server_id
return await _exchange_and_connect(state, code, request)
# Check if this callback belongs to a legacy Google flow vs expired generic flow
if _is_legacy_google_flow(state):
require_admin(request)
return await _exchange_and_connect(state, code, request)
return HTMLResponse(_oauth_result_page(
"Authorization Expired",
"This authorization round has expired. Press Reconnect in Odysseus and try again.",
), status_code=400)
@router.post("/oauth/exchange/{server_id}")
async def oauth_exchange(server_id: str, request: Request, callback_url: str = Form(...)):
@@ -514,18 +538,28 @@ def setup_mcp_routes(mcp_manager: McpManager):
except Exception:
return HTMLResponse(_oauth_result_page("Error", "Invalid URL format."), status_code=400)
state = params.get("state", [None])[0]
if not state:
return HTMLResponse(_oauth_result_page("Error", "No OAuth state parameter found in the callback URL."), status_code=400)
# Generic MCP OAuth: if the pasted URL carries a state we are waiting on,
# resolve it directly (the background connect finishes the handshake).
state = params.get("state", [None])[0]
from src.mcp_oauth import resolve_pending
if state and resolve_pending(state, code):
if resolve_pending(state, code):
return HTMLResponse(_oauth_result_page(
"Authorization Successful",
"The MCP server is connecting. You can close this window and return to Odysseus.",
success=True,
))
return await _exchange_and_connect(server_id, code, request)
# Check if this exchange belongs to a legacy Google flow vs expired generic flow
if _is_legacy_google_flow(server_id):
return await _exchange_and_connect(server_id, code, request)
return HTMLResponse(_oauth_result_page(
"Authorization Expired",
"This authorization round has expired. Press Reconnect in Odysseus and try again.",
), status_code=400)
async def _exchange_and_connect(server_id: str, code: str, request: Request):
"""Exchange auth code for tokens and connect the MCP server."""