OAuth for Remote MCP Servers

How each AI assistant signs in to a remote MCP (Model Context Protocol) server, and why the flow differs by client and by where it runs.
Overview The protocol throughout is standard OAuth 2.1 — an open, widely implemented authorization standard. The human sign-in runs through oauth2-proxy, one of the most widely deployed open-source auth proxies; the only deployment-specific piece is a thin, spec-conforming authorization server (the /oauth endpoints) that hands MCP clients their tokens. Every client ends up the same way — a person signs in against Google (restricted to your organization's domain), and the client holds a short-lived bearer token it presents on each /mcp call. Two things differ between assistants: where the client runs (the user's machine — local — vs. the vendor's cloud), which decides where the OAuth callback lands; and what kind of OAuth client it is — a public client proving itself with PKCE (Proof Key for Code Exchange, which lets a client with no secret prove the token request comes from the same client that started the flow), or a confidential client proving itself with a secret.

The participants

The two axes

Local vs. cloud — where the client runs

Local clients (Claude Code, Cursor) run on the user's machine and receive the OAuth callback on loopback (127.0.0.1) — the authorization code never leaves the machine.

Cloud clients (Claude Desktop, Gemini Enterprise) run in the vendor's cloud, so the callback is a registered vendor URL — which is why the server has to be reachable from the internet.

Public vs. confidential — the OAuth client type

Public clients self-register at runtime (dynamic registration) and prove themselves with PKCE, no secret — Claude Code, Cursor, and Claude Desktop.

A confidential client is pre-provisioned once with a client_id + secret and proves itself with that secret — only Gemini Enterprise.

The configurations at a glance

AssistantClient runsOAuth callbackOAuth clientProves itself withNotes / requirements
Claude Code (CLI)local machineloopbackdynamic, publicPKCEworks once registered
Cursor (IDE)local machineloopbackdynamic, publicPKCEworks via the mcp-remote shim *
Claude DesktopAnthropic cloudvendor URL (claude.ai)dynamic, publicPKCEworks once registered
Gemini EnterpriseGoogle cloudvendor URL (Google)pre-provisioned, confidentialclient secretrequires a Gemini Enterprise license + admin connector registration
Cursor Cloud AgentsCursor cloudvendor URLdynamic / staticPKCE / secretrequires a Cursor team admin to add the server

* Cursor's local IDE connects through the mcp-remote shim — see its section.

Claude Code & Cursor — local

This is the local baseline: the client runs on the user's machine, so it completes the full OAuth flow with a loopback callback (127.0.0.1). It self-registers (dynamic registration), the loopback redirect is allowlisted, and the authorization code never leaves the machine. It is a public client: no secret — PKCE ties the token request back to the same client that started the flow.

sequenceDiagram
    autonumber
    participant CLI as Claude Code or Cursor
    participant PX as oauth2-proxy
    participant MM as MCP server
    participant G as Google
    Note over CLI,G: The client runs on the user's machine
    CLI->>MM: POST /oauth/register (loopback redirect_uri)
    MM-->>CLI: 201 client_id and no secret (loopback allowlisted)
    CLI->>PX: open browser to /oauth/authorize
    PX->>G: run Google sign-in
    G-->>PX: organization-domain user verified
    PX->>MM: forward /oauth/authorize with the verified identity
    MM-->>CLI: 302 to the loopback callback with a code
    CLI->>MM: POST /oauth/token (code + PKCE verifier)
    MM-->>CLI: access token
    CLI->>MM: call /mcp with Authorization Bearer token
    MM-->>CLI: tool results
      
Local PKCE flow — everything but the Google sign-in stays on the user's machine.

The Cursor exception

Cursor follows the same local flow, but a known Cursor bug stops the IDE from opening the browser after it registers — so the sign-in step never starts. The workaround is the mcp-remote shim (npx -y mcp-remote@latest https://mcp.example.com/mcp), which runs the OAuth flow itself and hands Cursor a working connection. Nothing on the server changes.

Claude Desktop — cloud

Claude Desktop's connector runs in Anthropic's cloud. It is still a public client: it discovers the server's endpoints and registers itself dynamically (PKCE, no secret), exactly like the local clients — the only difference is that the callback is a cloud URL (claude.ai) instead of loopback, so the authorization code — minted by your server, not by Google — transits Anthropic's servers. The person still signs in with their organization-domain Google account in the browser.

sequenceDiagram
    autonumber
    participant U as User browser
    participant CD as Claude (Anthropic cloud)
    participant PX as oauth2-proxy
    participant MM as MCP server
    participant G as Google
    Note over CD,MM: Claude discovers and registers itself — PKCE public client, no secret
    CD->>MM: GET /.well-known + POST /oauth/register
    MM-->>CD: discovery docs + 201 client_id (no secret)
    U->>PX: GET /oauth/authorize (opened by Claude)
    PX->>G: run Google sign-in
    G-->>PX: organization-domain user verified
    PX->>MM: forward /oauth/authorize with the verified identity
    MM-->>U: 302 to Claude's redirect (claude.ai) with a code
    U->>CD: the code lands on Anthropic's servers
    CD->>MM: POST /oauth/token (code + PKCE verifier)
    MM-->>CD: access token
    CD->>MM: call /mcp with Authorization Bearer token
    MM-->>CD: tool results
      
Cloud PKCE flow — like the local one, but the callback and token-bearing calls originate from Anthropic's cloud, so the server must be reachable from the internet.

Gemini Enterprise — cloud, confidential requires license + admin registration

Gemini Enterprise is the one confidential client. Instead of registering itself at runtime, an admin mints a client_id + secret once (out of band) and enters them into the Gemini Enterprise connector config. The connector runs in Google's cloud. The human still signs in (legs 1–6); then, server-to-server with no browser, Google's cloud exchanges the code for a token using its secret (leg 7) and calls /mcp (leg 9). This path requires a Gemini Enterprise license and admin registration of the connector on the Google side.

sequenceDiagram
    autonumber
    participant U as User browser
    participant V as Google cloud
    participant PX as oauth2-proxy
    participant MM as MCP server
    participant G as Google
    Note over V,MM: Pre-provisioned client_id + secret (registered once, not at runtime)
    Note over U,G: Per-user sign-in
    U->>PX: GET /oauth/authorize (sent by the connector)
    PX->>G: run Google sign-in
    G-->>PX: organization-domain user verified
    PX->>MM: forward /oauth/authorize with the verified identity
    MM-->>U: 302 to the connector's redirect_uri with a code
    U->>V: the code lands on Google's servers
    Note over V,MM: Token exchange and tool call
    V->>MM: POST /oauth/token (code + client secret)
    MM-->>V: access token
    V->>MM: call /mcp with Authorization Bearer token
    MM-->>V: tool results
      
Confidential cloud flow — the connector proves itself with a pre-shared secret at token exchange (leg 7) rather than PKCE. Note the two "Googles": Google cloud is the connector (the OAuth client, redirecting through vertexaisearch.cloud.google.com); Google is the identity provider that signs the person in.

Cursor Cloud Agents — cloud requires team admin

Cursor's Cloud Agents would connect from Cursor's cloud like Gemini Enterprise and Claude Desktop (Streamable HTTP, with OAuth). But adding the server is gated by Cursor's own permissions — only a Cursor team admin can add an MCP server to the team ("Only team admins can manage the default team marketplace"). Until an admin adds the server, no OAuth flow runs, so there is no completed flow to diagram. This is a vendor-side gate, not a property of the OAuth design.

Connecting a client

What to type into each assistant once the server is deployed. Every path ends the same way: a browser opens and the person signs in with their organization Google account.

References

The OAuth 2.1 backbone here is well-trodden: the authorization flow, PKCE, dynamic client registration, and Protected Resource Metadata discovery all follow the published standard and the common explainers.

The takeaway

The protocol is not the interesting part — the standard is borrowed and identical for every client. What varies, and what this comparison maps, are the two axes that decide everything else: where the client runs (local vs. cloud), which fixes where the OAuth callback lands; and how it proves itself (PKCE vs. a pre-shared secret), which fixes whether it can self-register or must be provisioned by an admin. The deployment shape that makes this work is worth naming: front the server with oauth2-proxy for the Google sign-in, place a thin spec-conforming authorization server behind it, and serve every client from a single internet-reachable host — the OAuth callback must be public for cloud clients anyway, and one host keeps the topology simple. Authentication, not network placement, is the boundary. Within that shape, only a confidential client (Gemini Enterprise) needs a pre-shared secret, and the practical friction is rarely the protocol — it is vendor-side gates such as a client browser-open bug or a team-admin permission on adding the server.