Prerequisites
Install Method
Docker
Operating System
macOS (Apple Silicon), Docker Desktop
Steps to Reproduce
- Self-host Odysseus where the UI origin and the OAuth redirect origin differ — e.g. UI on a LAN address,
OAUTH_REDIRECT_BASE_URL on a public tunnel (required, since most providers demand HTTPS).
- Add a remote MCP server that uses OAuth (Streamable HTTP).
- Connect it.
Expected Behaviour
The panel shows an authorization link, the user approves in the browser, the callback resolves the pending flow, and the server connects.
Actual Behaviour
The flow can never complete. Four independent defects cooperate — fixing any one alone still leaves it broken:
- The authorization link is never rendered. The backend publishes
auth_url on /api/mcp/servers, but no frontend version draws it. Every round waits out its window for a click no human can see, then surfaces as unhandled errors in a TaskGroup (1 sub-exception).
- The admin cookie does not travel to the callback origin. OAuth redirect endpoints are user-agent-facing (RFC 6749 §3.1.2); the browser arrives at the callback without the session cookie it holds for the UI origin.
AuthMiddleware rejects the callback before the route runs, returning {"error":"Not authenticated"} (401).
- The 300 s authorization window is shorter than a real login. Providers that authenticate by emailed link routinely take longer, so rounds expire mid-login and mint an endless re-authorization loop.
Logs / Screenshots
MCP OAuth: server <id> awaiting authorization (state=...)
... exactly 300s later ...
MCP OAuth flow error: unhandled errors in a TaskGroup (1 sub-exception)
Additional Information
All four fixes are running here and verified end to end (connected: meadow — 27 tools):
- MCP panel renders a "Needs authorization" row with an Authorize link, then polls until the row flips to Connected.
-
-
require_admin moved below pending-state resolution in the callback route, and /api/mcp/oauth/callback added to the auth-exempt list. This does not weaken the route: the unguessable in-flight state is the credential, and a callback whose state matches no pending flow still falls through to the admin-gated legacy path. Verified from the public internet — a forged callback gets 403, not a resolved flow. Same trust model as the existing task-webhook exemption.
- Window raised 300 → 1800 s.
Happy to split this into four PRs if you prefer them separate; reported together because the failure is only understandable as a chain.
Are you willing to submit a fix?
Yes — the fix described above is running in production here and I can open a PR.
Prerequisites
devbranch.Install Method
Docker
Operating System
macOS (Apple Silicon), Docker Desktop
Steps to Reproduce
OAUTH_REDIRECT_BASE_URLon a public tunnel (required, since most providers demand HTTPS).Expected Behaviour
The panel shows an authorization link, the user approves in the browser, the callback resolves the pending flow, and the server connects.
Actual Behaviour
The flow can never complete. Four independent defects cooperate — fixing any one alone still leaves it broken:
auth_urlon/api/mcp/servers, but no frontend version draws it. Every round waits out its window for a click no human can see, then surfaces asunhandled errors in a TaskGroup (1 sub-exception).AuthMiddlewarerejects the callback before the route runs, returning{"error":"Not authenticated"}(401).Logs / Screenshots
Additional Information
All four fixes are running here and verified end to end (
connected: meadow — 27 tools):require_adminmoved below pending-state resolution in the callback route, and/api/mcp/oauth/callbackadded to the auth-exempt list. This does not weaken the route: the unguessable in-flightstateis the credential, and a callback whose state matches no pending flow still falls through to the admin-gated legacy path. Verified from the public internet — a forged callback gets 403, not a resolved flow. Same trust model as the existing task-webhook exemption.Happy to split this into four PRs if you prefer them separate; reported together because the failure is only understandable as a chain.
Are you willing to submit a fix?
Yes — the fix described above is running in production here and I can open a PR.