Building MCP servers
Authenticating MCP servers
Governing MCP across your organization?
Speakeasy’s AI control plane connects, secures, and observes every agent and MCP server from one entry point — with SSO, RBAC, runtime guardrails, and a full audit trail.
The Model Context Protocol (MCP) Specification is clear about what is expected from MCP servers to implement OAuth support. Those words may seem reassuring, especially if you already implement OAuth in your products.
However, your OAuth server most likely doesn’t comply with MCP’s requirements. The MCP Specification expects authorization servers to meet a highly specific set of OAuth features that many OAuth servers do not support.
This mismatch between current OAuth implementations and what MCP expects is a common point of friction holding back enterprise adoption of MCP. If you’re still in the exploratory phase of MCP development for your product, you may not have bumped into this problem yet. The OAuth situation usually only rears its head when you get to the point at which you consider the practical steps a user will take when authenticating with your service and authorizing the MCP server to act on their behalf.
In this guide, we’ll explain this mismatch in plain terms, offer alternatives, and hopefully help you find your way.
The perfect world that doesn’t exist
In an ideal universe, here’s how MCP OAuth would work: Your company already has an OAuth server1 that perfectly complies with the MCP Specification. All you need to do is host a well-formed /.well-known/oauth-protected-resource file on your MCP server, and you’re done! The MCP client handles the entire OAuth flow automatically.
The June 2025 revision of the
specification
settled how discovery works: the MCP server is an OAuth resource server that
publishes /.well-known/oauth-protected-resource (RFC 9728) and returns it
via the WWW-Authenticate header on a 401. The client reads that document
to find the separate authorization server, then fetches the authorization
server’s own /.well-known/oauth-authorization-server metadata (RFC 8414).
Current MCP clients follow this flow, but you’ll still find older servers and
clients that expected the MCP server to be its own authorization server.
In reality, however, only a handful of OAuth providers have added support for the specific features required by the MCP Specification. Most OAuth providers would need to change their implementation significantly to support MCP’s requirements.
The specification splits the requirements across the two roles. Your MCP server (the resource server) must publish Protected Resource Metadata (RFC 9728), and clients must send the RFC 8707 resource parameter. Your authorization server needs to support:
- OAuth 2.1 with mandatory PKCE2
- Authorization Server Metadata (RFC 8414)
- Dynamic Client Registration (DCR)3, which clients and authorization servers should support
That last item, Dynamic Client Registration, is where the friction usually starts. It’s a recommendation rather than a hard requirement, but without it clients can’t onboard automatically, and support for it is uneven across OAuth providers.
The DCR problem
When creating an OAuth client, the usual workflow is for the developer to manually get a client_id and client_secret from the authorization server, then configure their application with these credentials.
This traditional approach is aimed at developers, not end users. It assumes that the developer has access to the OAuth server’s management interface and can create an application manually.
The idea of end users finding your application’s OAuth registration page, filling out a form, and getting a client ID and secret is not how the MCP Specification envisions OAuth. Instead, it expects that MCP clients can dynamically register themselves as OAuth clients without manual intervention.
Here’s the crux of the issue: MCP clients have no standardized way to handle client IDs or client secrets4. They need to be able to register themselves dynamically as OAuth clients to perform the authentication dance.
But here’s the thing, and it cannot be stressed enough: many OAuth providers still don’t support Dynamic Client Registration (and yours may not either).
Support is improving as MCP adoption grows, and some providers and identity platforms (such as WorkOS and Auth0) now offer it, but the big consumer identity providers you’d reach for first have historically not. Google, GitHub, and Microsoft Entra ID (formerly Azure AD) have all lacked open DCR, and your company’s homegrown OAuth solution that Bob from IT built five years ago almost certainly doesn’t have it either.
If you’re thinking, “But wait, I’ve seen MCP servers with OAuth!” you’re right, but most are not doing what you think they’re doing.
The OAuth proxy solution
There’s a solution, but it isn’t pretty. Since most OAuth providers don’t support DCR, the community has come up with a workaround: OAuth proxies.
An OAuth proxy sits between the MCP client and your actual OAuth provider. Think of it as a translator that speaks “MCP OAuth” on one side and “regular OAuth” on the other. The most popular implementation uses Cloudflare Workers5, which has become the de facto standard for this architectural pattern.
Here’s how it works:
- The proxy exposes its own OAuth endpoints that comply with MCP requirements (including DCR support).
- The MCP client only talks to the proxy, never directly to your real OAuth provider.
- The proxy handles all the translation between MCP’s expectations and your OAuth provider’s reality.
- Fake tokens all the way down: The tokens the MCP client receives are generated by the proxy; they never see your actual OAuth provider’s tokens.
The Cloudflare OAuth wrapper exposes endpoints like:
/.well-known/oauth-authorization-server/register(for dynamic client registration)/authorize/token
Behind the scenes, the proxy stores all the mapping data in Cloudflare KV6, maintains the relationship between its “fake” tokens and the real provider tokens, and handles all the complexity of token refresh and validation.
This is quite a bit of responsibility for a proxy to take on, and the implications for your security posture are significant. You’re essentially building an entire OAuth authorization server to bridge the gap between MCP’s requirements and existing OAuth providers.
The hidden complexity of OAuth proxies
Building an OAuth proxy isn’t just a weekend project. You need to handle:
- PKCE verification7 (because MCP requires OAuth 2.1)
- Token lifecycle management (storing, refreshing, and revoking)
- Security considerations (you’re now responsible for token security)
- Custom adapters (for each OAuth provider you want to support)
- Performance optimization (every auth check now has additional hops)
The fun doesn’t stop there.
The illusion of Dynamic Client Registration
Most current implementations store a single client_id and client_secret for the entire proxy. This is how both Cloudflare’s proxy and Stytch currently work.
Despite the technical implementation of Dynamic Client Registration within the proxy, from the upstream OAuth provider’s perspective, there’s only one registered application.
This means every user of your MCP server is effectively acting under the same OAuth application. It’s not true Dynamic Client Registration in the sense that each MCP client gets its own distinct OAuth application identity with the upstream provider. Depending on your use case, this might be fine, or it might be a compliance nightmare.
Up until now, your customers have been using your OAuth server with their own client IDs and secrets; but now they all share the same credentials. This can lead to issues with rate limiting, token revocation, and auditing.
Simply plopping a Cloudflare OAuth proxy in front of your MCP server doesn’t magically solve these problems. You may need to make significant changes to your own systems to accommodate this new architecture.
MCP OAuth example: WorkOS and Cloudflare Workers
The Cloudflare AI repository provides several live examples demonstrating OAuth proxy implementations. The WorkOS example illustrates how an OAuth wrapper is registered using the Cloudflare provider, with custom adapters that enable the Cloudflare OAuth server to proxy requests to WorkOS.
After analyzing the complete Cloudflare provider source implementation, several key architectural patterns emerge that are consistent across all examples:
-
The Cloudflare wrapper exposes its own complete OAuth, including
.well-known/oauth-authorization-server,register,authorize, andtokenendpoints. The MCP client never directly interacts with the downstream OAuth provider and remains unaware of the actual provider’s server details. -
All server metadata returned in
well-known/oauth-authorization-serverpoint exclusively to the Cloudflare server. The authorization codes and access tokens received by the MCP client are proxy tokens generated by the Cloudflare wrapper, completely isolated from the underlying WorkOS tokens. -
The Cloudflare provider uses Cloudflare KV for persistent storage of grant data, authorization codes, and provider access tokens.
-
Despite the proxy architecture, the proxy still redirects users to the authentic OAuth consent screen of the underlying provider.
-
The Cloudflare provider handles PKCE verification, ensuring OAuth 2.1 security standards are maintained throughout the proxy flow.
-
Custom WorkOS adapters integrate with the Cloudflare OAuth wrapper through
OAUTH_PROVIDER.parseAuthRequestandOAUTH_PROVIDER.completeAuthorizationfunctions. ThecompleteAuthorizationmethod allows the Cloudflare wrapper to complete a token exchange with the real OAuth server, store the resulting token in KV storage, and subsequently initiate the MCP client callback. The final token exchange occurs exclusively between the MCP client and the Cloudflare wrapper. -
On the WorkOS side, you configure your Cloudflare MCP domain callback URL as a valid redirect URL in WorkOS and use your single WorkOS
ClientIDandClientSecretin the server adapter. Regardless of how many MCP clients connect, they all funnel through one WorkOS OAuth application registration.
How MCP servers use OAuth today
So who is actually using OAuth with MCP servers today? The answer reveals a lot about the state of the ecosystem.
Many companies claiming to support “OAuth” for their MCP servers are actually doing something much simpler. GitHub, for example, has extensive documentation about hosting MCP servers remotely. But when you get to the authentication section, you’ll find this note: “Dynamic Client Registration is NOT supported by Remote GitHub MCP Server at this time.”
Instead, they expect users to obtain a personal access token separately and pass it directly in headers to their MCP server. This is OAuth in name only - there’s no OAuth flow happening through the MCP client at all. This is actually a perfectly valid approach! Sometimes the simplest solution is best. But it’s not what the MCP Specification envisions.
The companies that are supporting true MCP OAuth are almost universally using the Cloudflare Workers proxy pattern or have built something similar themselves.
The architectural implications of OAuth proxies
Building an OAuth proxy means you’re essentially building a distributed authorization server. Your proxy needs to handle all the stateful operations that OAuth requires: storing authorization codes, managing token lifecycles, and maintaining secure mappings between proxy tokens and real provider tokens.
Every API call to your MCP server now involves additional hops through the proxy. Token validation that used to be a simple database lookup now involves multiple service calls.
Security gets more complex. You’re now responsible for securing the proxy infrastructure itself, protecting the token mapping database, ensuring proper token rotation and expiration, and all the edge cases that come with handling OAuth tokens.
You also may need to update your logging and monitoring practices. When all users share the same OAuth application credentials, depending on your current setup, you may lose granular visibility into who’s doing what. Your audit logs show one application making all the requests.
These are not trivial changes. They require careful planning, additional resources, and a deep understanding of both OAuth and MCP.
The developer experience gap
This brings us to MCP auth’s most difficult challenge: the mismatch between developer expectations and reality.
Developers coming to MCP expect OAuth to work like it does everywhere else: get your client credentials, configure your app, and done. Instead, they find themselves in a quagmire of proxy architectures, token mapping strategies, and the intricacies of DCR.
What should be a simple authentication problem has turned into an architectural redesign that touches security, performance, and compliance.
Looking forward
The June 2025 revision of the specification settled the biggest architectural question by making the MCP server a plain OAuth resource server that delegates to a separate authorization server, discovered through Protected Resource Metadata (RFC 9728) and bound to its audience with Resource Indicators (RFC 8707). That removes the ambiguity about whether your MCP server should also be an authorization server: it shouldn’t.
What the specification can’t fix on its own is uneven Dynamic Client Registration support across existing OAuth providers, which is why the OAuth proxy pattern is still common. As more identity platforms add DCR and ship MCP-aware authorization servers, expect the proxy to fade for greenfield builds. For now, teams implementing MCP still need to weigh how much of the flow they run themselves against what they can delegate to a provider that already speaks the spec.
Footnotes
-
An OAuth server (or authorization server) is the system that handles user authentication and issues access tokens. Think of it as the bouncer at a club who checks IDs and gives out wristbands. ↩
-
PKCE (Proof Key for Code Exchange) is a security extension to OAuth that prevents authorization code interception attacks. It’s like adding a secret handshake to the authentication process. ↩
-
Dynamic Client Registration (DCR) allows applications to register themselves as OAuth clients programmatically, without manual configuration. Imagine if every app could automatically add itself to your Google account’s authorized apps list - that’s DCR. ↩
-
Client IDs and client secrets are like username-and-password pairs for applications (not users). They identify which application is requesting access to user data. ↩
-
Cloudflare Workers are serverless functions that run at the edge of Cloudflare’s network. Think of them as tiny programs that can intercept and modify web requests. ↩
-
Cloudflare KV is a key-value storage system. Essentially, it’s a simple database for storing data like tokens and their mappings. ↩
-
PKCE verification ensures that the same client that started the OAuth flow is the one completing it, preventing certain types of attacks. ↩