diff --git a/website/docs/guides/oauth-over-ssh.md b/website/docs/guides/oauth-over-ssh.md index 085ba8a2924..15ac3668f6f 100644 --- a/website/docs/guides/oauth-over-ssh.md +++ b/website/docs/guides/oauth-over-ssh.md @@ -1,12 +1,12 @@ --- sidebar_position: 17 title: "OAuth over SSH / Remote Hosts" -description: "How to complete browser-based OAuth (xAI, Spotify) when Hermes runs on a remote machine, container, or behind a jump box" +description: "How to complete browser-based OAuth (xAI, Spotify, MCP servers) when Hermes runs on a remote machine, container, or behind a jump box" --- # OAuth over SSH / Remote Hosts -Some Hermes providers — currently **xAI Grok OAuth** and **Spotify** — use a *loopback redirect* OAuth flow. The auth server (xAI, Spotify) redirects your browser to `http://127.0.0.1:/callback` so a tiny HTTP listener started by the `hermes auth ...` command can grab the authorization code. +Some Hermes providers — **xAI Grok OAuth**, **Spotify**, and **remote MCP servers** (Linear, Sentry, Atlassian, Asana, Figma, …) — use a *loopback redirect* OAuth flow. The auth server redirects your browser to `http://127.0.0.1:/callback` so a tiny HTTP listener started by Hermes can grab the authorization code. This works perfectly when Hermes and your browser are on the same machine. It breaks the moment they aren't: your laptop's browser tries to reach `127.0.0.1` on **your laptop**, but the listener is bound to `127.0.0.1` on **the remote server**. @@ -50,12 +50,44 @@ Hermes uses the **same PKCE verifier, state and nonce** for both paths, so the u |----------|---------------|----------------| | `xai-oauth` (Grok SuperGrok) | `56121` | Yes, when Hermes is remote | | Spotify | `43827` | Yes, when Hermes is remote | +| MCP servers (`auth: oauth`) | auto-picked per server | Yes, when Hermes is remote | | `anthropic` (Claude Pro/Max) | n/a | No — paste-the-code flow | | `openai-codex` (ChatGPT Plus/Pro) | n/a | No — device code flow | | `minimax`, `nous-portal` | n/a | No — device code flow | If your provider isn't in the table, you don't need a tunnel. +## MCP Servers + +Remote MCP servers (Linear, Sentry, Atlassian, Asana, Figma, etc.) use the same loopback redirect flow. Hermes auto-picks a free port per server and prints the authorize URL when the OAuth flow kicks off — either at startup (when a new server appears in `mcp_servers:`) or when you run `hermes mcp login `. + +You have two ways to complete it from a remote host: + +**Option 1 — paste the redirect URL back (no setup, works anywhere).** On an interactive terminal, Hermes prompts you to paste the redirect URL alongside running the local listener. After approving in your browser, the redirect to `http://127.0.0.1:/callback` will show a connection error — that's expected. Copy the **full URL from the browser's address bar** and paste it at the Hermes prompt: + +``` + MCP OAuth: authorization required. + Open this URL in your browser: + + https://mcp.linear.app/authorize?response_type=code&... + + Or paste the redirect URL here (or the ?code=...&state=... portion) and press Enter: +> https://mcp.linear.app/callback?code=abc123&state=xyz + Got authorization code from paste — completing flow. +``` + +A bare `?code=...&state=...` query string is accepted too. This works for any MCP server with `auth: oauth` and requires no SSH config changes. + +**Option 2 — SSH port forward (same as xAI / Spotify).** Hermes prints the exact port it bound to in the SSH-session hint. Open a separate terminal on your laptop: + +```bash +ssh -N -L :127.0.0.1: user@remote-host +``` + +Then open the authorize URL in your browser as normal; the redirect tunnels through and the listener picks it up. Use this when you need the flow to complete unattended (e.g. scripted re-auth where you can't paste interactively). + +**Pitfall — the 30s config-reload race.** If you edit `~/.hermes/config.yaml` to add an OAuth MCP server from inside a running Hermes session, the CLI auto-reloads MCP connections with a 30s timeout. That's not enough time to complete an interactive OAuth flow, and the reload will give up. Use `hermes mcp login ` from a fresh terminal instead — it has no such cap and waits the full 5 min for you to paste back. + ## Why the listener can't just bind 0.0.0.0 xAI and Spotify both validate the `redirect_uri` parameter against an allowlist. Both require the loopback form (`http://127.0.0.1:/callback`). Binding the listener to `0.0.0.0` or a different port would cause the auth server to reject the request as a redirect_uri mismatch. The SSH tunnel keeps the loopback URI intact end-to-end. @@ -151,4 +183,5 @@ The tokens are written under the Linux user that ran `hermes auth add ...`. If y - [xAI Grok OAuth](./xai-grok-oauth.md) - [Spotify (`Running over SSH`)](../user-guide/features/spotify.md#running-over-ssh--in-a-headless-environment) +- [Native MCP client (OAuth section)](../user-guide/features/mcp.md#oauth-authenticated-http-servers) - [SSH `-J` / ProxyJump (man page)](https://man.openbsd.org/ssh#J) diff --git a/website/docs/user-guide/features/mcp.md b/website/docs/user-guide/features/mcp.md index 72b6fc8a7c4..12142b650ee 100644 --- a/website/docs/user-guide/features/mcp.md +++ b/website/docs/user-guide/features/mcp.md @@ -89,6 +89,28 @@ Use HTTP servers when: - your organization exposes internal MCP endpoints - you do not want Hermes spawning a local subprocess for that integration +### OAuth-authenticated HTTP servers + +Most hosted MCP servers (Linear, Sentry, Atlassian, Asana, Figma, Stripe, …) require OAuth 2.1 instead of a static bearer token. Set `auth: oauth` and Hermes handles discovery, dynamic client registration, PKCE, token exchange, refresh, and step-up auth via the MCP Python SDK. + +```yaml +mcp_servers: + linear: + url: "https://mcp.linear.app/mcp" + auth: oauth +``` + +On first connect, Hermes prints an authorize URL, opens your browser when possible, and waits for the OAuth callback on a local loopback port. Tokens are cached at `~/.hermes/mcp-tokens/.json` with 0o600 perms; subsequent runs reuse them silently until refresh fails. + +**Remote / headless hosts.** When Hermes runs on a different machine than your browser, the loopback callback can't reach your laptop. Two ways to complete the flow: + +- **Paste-back (no setup):** on an interactive terminal Hermes prints "Or paste the redirect URL here…" alongside the authorize URL. Open the URL in your browser, approve, copy the full URL the browser ends up on (the redirect will show a connection error — that's expected), paste it at the prompt. Bare `?code=…&state=…` query strings work too. +- **SSH port forward:** `ssh -N -L :127.0.0.1: user@host` in a separate terminal, then let the redirect flow normally. + +See [OAuth over SSH / Remote Hosts](../../guides/oauth-over-ssh.md#mcp-servers) for the full walkthrough, including DCR-less servers (e.g. Slack), pre-registered `client_id`/`client_secret`, scope customization, and re-auth via `hermes mcp login `. + +**Pitfall — config auto-reload race.** When you edit `~/.hermes/config.yaml` from inside a running Hermes session, the CLI auto-reloads MCP connections with a 30s timeout. That's not enough for an interactive OAuth flow. Add the entry, then run `hermes mcp login ` from a fresh terminal — it waits the full 5 minutes for you to complete auth. + ## Basic configuration reference Hermes reads MCP config from `~/.hermes/config.yaml` under `mcp_servers`.