mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-28 07:35:27 -04:00
fix(mcp): share oauth redirect URI (#4087)
This commit is contained in:
+18
-6
@@ -108,6 +108,12 @@ def _load_disabled_map():
|
|||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _mcp_oauth_redirect_uri() -> str:
|
||||||
|
"""Shared callback URL for legacy Google and generic MCP OAuth flows."""
|
||||||
|
from src.mcp_oauth import REDIRECT_URI
|
||||||
|
return REDIRECT_URI
|
||||||
|
|
||||||
|
|
||||||
def setup_mcp_routes(mcp_manager: McpManager):
|
def setup_mcp_routes(mcp_manager: McpManager):
|
||||||
"""Setup MCP routes with the provided manager."""
|
"""Setup MCP routes with the provided manager."""
|
||||||
|
|
||||||
@@ -445,9 +451,9 @@ def setup_mcp_routes(mcp_manager: McpManager):
|
|||||||
client_id = keys["client_id"]
|
client_id = keys["client_id"]
|
||||||
scopes = oauth_cfg.get("scopes", [])
|
scopes = oauth_cfg.get("scopes", [])
|
||||||
|
|
||||||
# For Desktop App creds, redirect to localhost — the user will
|
# For Desktop App creds, default to localhost — the user will
|
||||||
# paste the resulting URL back if they're on a different device.
|
# paste the resulting URL back if they're on a different device.
|
||||||
redirect_uri = "http://localhost:7000/api/mcp/oauth/callback"
|
redirect_uri = _mcp_oauth_redirect_uri()
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"client_id": client_id,
|
"client_id": client_id,
|
||||||
@@ -469,7 +475,7 @@ def setup_mcp_routes(mcp_manager: McpManager):
|
|||||||
return RedirectResponse(auth_url)
|
return RedirectResponse(auth_url)
|
||||||
else:
|
else:
|
||||||
# Remote device — show paste-back page
|
# Remote device — show paste-back page
|
||||||
return HTMLResponse(_oauth_authorize_page(auth_url, server_id, host))
|
return HTMLResponse(_oauth_authorize_page(auth_url, server_id, host, redirect_uri))
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@@ -536,7 +542,7 @@ def setup_mcp_routes(mcp_manager: McpManager):
|
|||||||
client_id = keys["client_id"]
|
client_id = keys["client_id"]
|
||||||
client_secret = keys["client_secret"]
|
client_secret = keys["client_secret"]
|
||||||
|
|
||||||
redirect_uri = "http://localhost:7000/api/mcp/oauth/callback"
|
redirect_uri = _mcp_oauth_redirect_uri()
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
@@ -603,13 +609,19 @@ def setup_mcp_routes(mcp_manager: McpManager):
|
|||||||
return router
|
return router
|
||||||
|
|
||||||
|
|
||||||
def _oauth_authorize_page(auth_url: str, server_id: str, host: str) -> str:
|
def _oauth_authorize_page(
|
||||||
|
auth_url: str,
|
||||||
|
server_id: str,
|
||||||
|
host: str,
|
||||||
|
redirect_uri: str = "http://localhost:7000/api/mcp/oauth/callback",
|
||||||
|
) -> str:
|
||||||
"""Page with Google sign-in link and URL paste-back form for remote access."""
|
"""Page with Google sign-in link and URL paste-back form for remote access."""
|
||||||
# Escape values interpolated into the page: `host` comes from the request
|
# Escape values interpolated into the page: `host` comes from the request
|
||||||
# Host header and `server_id` from the OAuth state — neither is trusted.
|
# Host header and `server_id` from the OAuth state — neither is trusted.
|
||||||
auth_url = html.escape(auth_url, quote=True)
|
auth_url = html.escape(auth_url, quote=True)
|
||||||
server_id = html.escape(server_id, quote=True)
|
server_id = html.escape(server_id, quote=True)
|
||||||
host = html.escape(host, quote=True)
|
host = html.escape(host, quote=True)
|
||||||
|
redirect_uri = html.escape(redirect_uri, quote=True)
|
||||||
return f"""<!DOCTYPE html>
|
return f"""<!DOCTYPE html>
|
||||||
<html><head>
|
<html><head>
|
||||||
<meta charset="UTF-8"><title>Authorize — Odysseus</title>
|
<meta charset="UTF-8"><title>Authorize — Odysseus</title>
|
||||||
@@ -654,7 +666,7 @@ def _oauth_authorize_page(auth_url: str, server_id: str, host: str) -> str:
|
|||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<form method="POST" action="http://{host}/api/mcp/oauth/exchange/{server_id}">
|
<form method="POST" action="http://{host}/api/mcp/oauth/exchange/{server_id}">
|
||||||
<p>Paste the URL from your browser after signing in:</p>
|
<p>Paste the URL from your browser after signing in:</p>
|
||||||
<input type="text" name="callback_url" placeholder="http://localhost:7000/api/mcp/oauth/callback?code=..." required>
|
<input type="text" name="callback_url" placeholder="{redirect_uri}?code=..." required>
|
||||||
<br><button type="submit">Connect</button>
|
<br><button type="submit">Connect</button>
|
||||||
</form>
|
</form>
|
||||||
</div></body></html>"""
|
</div></body></html>"""
|
||||||
|
|||||||
@@ -972,7 +972,7 @@ def test_mcp_oauth_page_escapes_reflected_values():
|
|||||||
src = Path(__file__).resolve().parents[1] / "routes" / "mcp_routes.py"
|
src = Path(__file__).resolve().parents[1] / "routes" / "mcp_routes.py"
|
||||||
text = src.read_text()
|
text = src.read_text()
|
||||||
body = text.split("def _oauth_authorize_page(", 1)[1].split("return f", 1)[0]
|
body = text.split("def _oauth_authorize_page(", 1)[1].split("return f", 1)[0]
|
||||||
for var in ("auth_url", "server_id", "host"):
|
for var in ("auth_url", "server_id", "host", "redirect_uri"):
|
||||||
assert f"{var} = html.escape({var}" in body, var
|
assert f"{var} = html.escape({var}" in body, var
|
||||||
|
|
||||||
|
|
||||||
@@ -981,6 +981,18 @@ def _import_mcp_routes():
|
|||||||
return importlib.import_module("routes.mcp_routes")
|
return importlib.import_module("routes.mcp_routes")
|
||||||
|
|
||||||
|
|
||||||
|
def test_google_mcp_oauth_uses_configured_redirect_base(monkeypatch):
|
||||||
|
monkeypatch.setenv("OAUTH_REDIRECT_BASE_URL", "https://odysseus.example/app/")
|
||||||
|
monkeypatch.delenv("APP_PUBLIC_URL", raising=False)
|
||||||
|
sys.modules.pop("src.mcp_oauth", None)
|
||||||
|
mcp_routes = _import_mcp_routes()
|
||||||
|
|
||||||
|
assert (
|
||||||
|
mcp_routes._mcp_oauth_redirect_uri()
|
||||||
|
== "https://odysseus.example/app/api/mcp/oauth/callback"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_mcp_oauth_paths_resolve_under_data_dir(tmp_path, monkeypatch):
|
def test_mcp_oauth_paths_resolve_under_data_dir(tmp_path, monkeypatch):
|
||||||
mcp_routes = _import_mcp_routes()
|
mcp_routes = _import_mcp_routes()
|
||||||
monkeypatch.setattr(mcp_routes, "MCP_OAUTH_DIR", str(tmp_path / "data" / "mcp_oauth"))
|
monkeypatch.setattr(mcp_routes, "MCP_OAUTH_DIR", str(tmp_path / "data" / "mcp_oauth"))
|
||||||
|
|||||||
Reference in New Issue
Block a user