If you’re building a remote MCP server that more than one person will use, you need OAuth — not an API key field, not a shared secret in an env var. MCP’s authorization framework is built on OAuth 2.1, and clients like Claude expect to walk through a standard flow, not a vendor-specific one. Here’s what actually goes into a working implementation, where builders get stuck, and the shortcut that skips most of it.
Why this isn’t optional for a remote server
A local, stdio MCP server runs on the same machine as the client, so it inherits whatever access the user already has — there’s no separate login step. A remote server is different: it’s a service other people’s AI clients connect to over HTTP, so it needs its own way to know who’s asking and what they’re allowed to do. MCP standardized on OAuth 2.1 for this rather than leaving every server to invent its own scheme — which is also why a compliant client can connect to any compliant server without custom code per vendor. (For the transport side of this distinction, see remote vs. local MCP servers.)
The pieces a working implementation needs
None of this is MCP-specific cryptography — it’s standard OAuth 2.1, applied to a new category of client. The parts that trip people up:
- PKCE, always. OAuth 2.1 drops the older implicit and password grants and requires Proof Key for Code Exchange even for confidential clients. If your authorization server still assumes a client secret is enough, it predates what MCP clients expect.
- Dynamic client registration (DCR). An AI client that has never talked to your server before needs a way to register itself and get a client ID on the spot — no email to your team, no manual approval queue. Skipping DCR is the single biggest reason a server rejects a client with an
unauthorized_clienterror; we cover that failure mode from the connecting side in this post. If you’d rather keep a manual allow-list for business reasons, that’s a legitimate choice — just expect every new client to hit that wall until you approve it. - Resource-scoped tokens. A token your server issues should be bound to your server specifically, not usable against some other API the same identity provider happens to protect. This is what stops a token minted for one MCP server from being replayed against a different one.
- Metadata discovery. Clients expect to find your authorization server and its capabilities by fetching well-known metadata documents, not by reading your docs and hardcoding endpoints. Skip this and every client integration becomes a support ticket.
Two ways teams actually ship this
Almost nobody hand-rolls an OAuth 2.1 authorization server from scratch anymore. The realistic options are:
- Delegate to an existing identity provider. Auth0, WorkOS, Clerk, and similar platforms already implement PKCE, DCR, and metadata discovery correctly; you configure your MCP server as a protected resource in front of it. This is the path most teams should take — OAuth correctness is not where you want to spend your novelty budget.
- Use an SDK that handles the MCP-specific wiring. The official MCP SDKs include authorization helpers that reduce the boilerplate, but they still assume you have an OAuth provider behind them. They save you from re-reading the spec, not from running one.
Rolling your own authorization server end to end is possible, but it’s the kind of security-critical code where a subtle bug — a missing audience check, a redirect URI validated with a substring match instead of an exact one — is invisible until someone finds it.
What you’re implicitly promising once it works
Getting the OAuth handshake to succeed is the easy half. The harder question is what a successful login actually grants. Most MCP servers today issue one token that covers every tool on the server, read and write alike — there’s no standard way yet to scope a grant down to “just the read-only tools.” If you’re designing tools for your own server, decide up front which ones are destructive and document that clearly, even if the OAuth scope itself can’t enforce the split. We go into what a scope actually does and doesn’t cover in MCP OAuth scopes explained.
The shortcut: don’t build the OAuth server at all
If what you actually want is to make an existing API callable by an AI client — your own product’s API, or a third party’s — you don’t have to stand up an authorization server just to get there. gate’s MCP Builder generates the tool definitions from an OpenAPI spec or a docs URL, and the resulting server runs through your gate gateway: there’s nothing to host or deploy. AI clients authenticate to gate itself, not to infrastructure you built; the API key or OAuth2 client credentials your server needs to call the target API are stored encrypted and injected at call time, never exposed in the tool list. That covers the case where you’re wrapping an API with a static credential. If what you’re building is more like “every one of my users needs to authorize their own account” — user-delegated OAuth to the target API — that’s still a real design problem you own, covered in more depth in turning an API into an MCP server.
Either way, once a server is live, it’s worth running it through a free security scan before pointing real users at it — the tool descriptions you or a generator wrote are exactly the surface prompt injection targets, and that’s a separate problem from whether your OAuth is correct.
The bottom line
A remote MCP server needs real OAuth 2.1 — PKCE, dynamic client registration, resource-scoped tokens, discoverable metadata — not a private API key stapled to a header. Delegate to an identity provider rather than hand-rolling the authorization server, decide which tools are destructive before you ship, and if all you need is to make an API callable, check whether generating the server gets you there without building the auth infrastructure yourself at all.