diff --git a/README.md b/README.md index 02d99dc..7a137b4 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Use a dedicated public hostname such as `mcp.ryankegel.com` and terminate HTTPS The MCP endpoint is `https://mcp.ryankegel.com/mcp`. The OAuth discovery and login endpoints share that hostname. The MCP allowed-host setting also prevents requests to unexpected hostnames. -5. In ChatGPT on the web, add a custom MCP server with that `/mcp` URL and choose OAuth authentication. The server supports Dynamic Client Registration (DCR). On the first authorization, sign in with `MCP_AUTH_USERNAME` and the password stored in `secrets/mcp_auth_password`, then approve the requested vault access. +5. In ChatGPT on the web, add a custom MCP server with that `/mcp` URL and choose OAuth authentication. The server supports Dynamic Client Registration (DCR). On the first authorization, sign in with `MCP_AUTH_USERNAME` and the password stored in `secrets/mcp_auth_password`, approve the requested vault access, then click **Continue** to return to ChatGPT. ## Enable writes diff --git a/oauth.py b/oauth.py index a07fbf0..72186e3 100644 --- a/oauth.py +++ b/oauth.py @@ -27,7 +27,7 @@ from mcp.server.auth.provider import ( from mcp.shared.auth import OAuthClientInformationFull, OAuthToken from starlette.exceptions import HTTPException from starlette.requests import Request -from starlette.responses import HTMLResponse, RedirectResponse, Response +from starlette.responses import HTMLResponse, Response ACCESS_TOKEN_SECONDS = 60 * 60 @@ -543,7 +543,23 @@ def make_login_routes(provider: LocalOAuthProvider) -> list[Any]: await provider.clear_login_failures(client_ip) redirect_url = await provider.finish_login(flow_id, username) - response = _protect_login_response(RedirectResponse(redirect_url, status_code=303)) + safe_redirect_url = html.escape(redirect_url, quote=True) + success_page = f""" + + + +Authorization complete + +

Authorization complete

+

Continue to return to the app that requested access.

+Continue +""" + response = _protect_login_response( + HTMLResponse(success_page) + ) response.delete_cookie(_csrf_cookie_name(flow_id), path="/", secure=True, httponly=True, samesite="strict") return response