Connecting to the HipTrip MCP server via OAuth

HipTrip's MCP server (/api/mcp) supports OAuth 2.1 with Dynamic Client Registration (RFC 7591), so a compatible harness — Claude Code, Claude Desktop, or any other MCP client that implements the MCP Authorization spec — can connect without you ever copying or storing a key.

This is additive: the static x-api-key / per-user API key flow documented in mcp-user-access.md still works unchanged and remains the only option for headless/non-interactive harnesses that can't complete a browser redirect.

What happens when you connect

  1. The harness calls POST /api/mcp with no credential and gets 401 with a WWW-Authenticate: Bearer resource_metadata="..." header.
  2. It fetches /.well-known/oauth-protected-resource/api/mcp, which points at this server as the authorization server.
  3. It fetches /.well-known/oauth-authorization-server for the actual endpoints, then POST /api/oauth/register to self-register — no manual setup, no client secret to distribute (HipTrip issues public clients only, proven via mandatory PKCE instead of a shared secret).
  4. It opens /oauth/authorize in a browser. You sign in (if not already) and see a consent screen listing exactly which scopes the application is requesting, then approve or deny.
  5. It exchanges the resulting authorization code at POST /api/oauth/token for a Bearer access token (1 hour) and refresh token (30 days).
  6. Every subsequent /api/mcp call sends Authorization: Bearer <token>.

Scopes

OAuth-issued tokens can only ever carry the same user scopes the manual per-user API key UI grants — trips:read, trips:write, packing:read, packing:write — never the editorial scopes (catalog:read, editorial:*, places:search). A client requesting more than that gets the intersection of what it asked for, what it's registered for, and what HipTrip's OAuth flow is willing to grant at all; it never gets an error for asking too much.

offline_access is advertised in discovery so clients that gate their own refresh-token request on seeing it (Claude Code) will ask for it — HipTrip issues a refresh token on every authorization either way.

Managing connections

Profile → Connected apps lists every application you've authorized — name, redirect host, granted scopes, last-used time — with a Disconnect button that immediately revokes every live token for that connection. This is independent of the API-key list above it; revoking one has no effect on the other.

For implementers: server-side pieces

  • lib/oauth/ — DCR validation, PKCE, redirect-URI matching, code/token minting and rotation, discovery metadata. See inline comments for the security reasoning (claim-before-validate ordering, refresh-token-family reuse detection, the loopback redirect-URI port exception, etc.).
  • app/oauth-discovery/oauth-authorization-server, app/oauth-discovery/openid-configuration, app/oauth-discovery/oauth-protected-resource, app/oauth-discovery/oauth-protected-resource/api/mcp — discovery documents, rewritten to their required external /.well-known/* URLs via next.config.mjs's rewrites() rather than living under a literal app/.well-known/ folder — that folder builds fine locally but its routes silently 404 once deployed to Vercel (confirmed: present in next build's own manifest and served correctly by next start, but 404 with x-matched-path: /404 — Vercel's build-output packaging drops routes under a dot-prefixed path segment). Pure functions of the deployment origin, no DB call.
  • app/oauth/authorize, app/api/oauth/consent — login+consent UI and its CSRF-safe signed-blob handoff (lib/oauth/consent-sign.ts).
  • app/api/oauth/register, app/api/oauth/token, app/api/oauth/revoke — the three protocol endpoints.
  • lib/mcp-auth.ts's authenticateMcpKey dispatches on credential prefix (htoac_ → OAuth access token, htmcp_ → static per-user key, else → legacy HIPTRIP_MCP_KEY) before doing any DB lookup.

Apply prisma/migrations/027_oauth_dcr/migration.sql manually to Aurora DSQL, one transaction at a time, same as every other migration in this repo.