0f5dd5c46e
The gateway's session-backed MCP OAuth flow (mcp.servers.oauth.start) binds its browser-callback listener on the BACKEND machine's 127.0.0.1. When the Desktop app connects to a remote backend (SSH/Tailscale), the user's browser resolves that loopback to the user's machine, the redirect dies, and every OAuth catalog server (ClickUp, Hospitable, ...) fails in-app with no working path — the exact topology from the 'MCP Recurring erros' support thread. Fix mirrors the Desktop's native gateway login (native-oauth-login.ts): - gateway: mcp.servers.oauth.start accepts client_redirect_uri (loopback-only, RFC 8252-style validation); when supplied no gateway listener is bound and the OAuth redirect_uri pins to the client's listener. - gateway: new mcp.servers.oauth.callback RPC relays the client-captured code/state into the flow; state verification stays in DashboardOAuthFlow.deliver_callback (constant-time compare, replay-safe). - desktop: mcp-oauth-callback-ipc.ts hosts a one-shot 127.0.0.1 listener in the main process (hermes:mcp-oauth:listen/wait/cancel via preload bridge). - desktop: hermes-bots mcp-setup.tsx prefers the client listener for local AND remote backends, falling back to the legacy gateway-listener flow on older gateways (feature-detect via start rejection). - docs: remote-host MCP OAuth section documents the automatic Desktop path. Validation: 19 new gateway tests (validator allowlist, listener skip, relay accept/reject/replay) — sabotage-verified; 5 new desktop tests against a real ephemeral listener; E2E through the real session registry + flow bridge with a stubbed provider probe; tsc electron+renderer builds clean.