/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 (a machine on the VPN —
private — vs. the vendor's cloud — public), which decides
the host it reaches; 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.
/oauth/authorize, /oauth/token, /oauth/register, .well-known discovery) and the /mcp tool endpoint. It mints codes and tokens, and validates a token on every /mcp call.Private/local clients (Claude Code, Cursor) run on a VPN-connected machine and reach the internal host mcp.internal.example with a loopback callback.
Public/cloud clients (Claude Desktop, Gemini Enterprise) run in the vendor's cloud and reach the public host mcp.example.com — which is why the server has to be reachable from the internet.
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.
| Assistant | Client runs | Host it reaches | OAuth client | Proves itself with | Notes / requirements |
|---|---|---|---|---|---|
| Claude Code (CLI) | local machine · VPN | internal host | dynamic, public | PKCE | works once registered |
| Cursor (IDE) | local machine · VPN | internal host | dynamic, public | PKCE | works via the mcp-remote shim * |
| Claude Desktop | Anthropic cloud | public host | dynamic, public | PKCE | works once registered |
| Gemini Enterprise | Google cloud | public host | pre-provisioned, confidential | client secret | requires a Gemini Enterprise license + admin connector registration |
| Cursor Cloud Agents | Cursor cloud | public host | dynamic / static | PKCE / secret | requires a Cursor team admin to add the server |
* Cursor's local IDE connects through the mcp-remote shim — see its section.
This is the local baseline: the client runs on the user's machine, so it completes the full OAuth
flow from inside the VPN 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, behind the VPN
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
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.internal.example/mcp), which
runs the OAuth flow itself and hands Cursor a working connection. Nothing on
the server changes.
Claude Desktop's connector runs in Anthropic's cloud, so it
reaches the public host. 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 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
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
and reaches the public host. 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
vertexaisearch.cloud.google.com); Google is the identity provider that signs the person in.Cursor's Cloud Agents would reach the public host 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.
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 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 (private/VPN vs. public/cloud), which fixes the host it reaches; 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 split the work across an internal host for VPN-local clients and a public host for cloud clients. 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.