Skip to content

Self-hosted multi-origin deployments cannot complete remote MCP OAuth (four cooperating defects) #5597

Description

@Ninthkitten

Prerequisites

  • I searched open issues and discussions and did not find an existing report of this bug.
  • This is not a security vulnerability.
  • I am running the latest code from the dev branch.

Install Method

Docker

Operating System

macOS (Apple Silicon), Docker Desktop

Steps to Reproduce

  1. 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).
  2. Add a remote MCP server that uses OAuth (Streamable HTTP).
  3. 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:

  1. 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).
  2. 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.
  3. AuthMiddleware rejects the callback before the route runs, returning {"error":"Not authenticated"} (401).
  4. 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):

  1. MCP panel renders a "Needs authorization" row with an Authorize link, then polls until the row flips to Connected.
      1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready for reviewDescription complete — ready for maintainer review

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions