Making sense of MCP auth
Of everything in the MCP spec, auth is the piece people have complained about the loudest. It has been called overengineered, underengineered, and confusing, sometimes all in the same thread. It is also genuinely important, and most of the frustration comes from a spec that has changed a lot in a short time in response to real pushback. So for episode four of the FastMCP podcast, Radhika Gulati (Product Marketing) sat down with Jeremiah Lowin (CEO) to untangle what MCP auth is actually doing, why it looks the way it does, and where it still falls short.
One disclaimer up front, which we made on the recording too: neither of us is a security expert, and this isn't our trade. What we do have is a lot of scar tissue from exposing this stuff to real users, and the good fortune of working with people who obsess over it for a living. We'll present the framing we think is most useful and ask forgiveness for the details we get wrong.
What follows is an edited version of that conversation.
Authentication and authorization are not the same thing
The word everyone shortens to "auth" actually finishes two different ways, and the split is the whole game. Authentication is who you are. Authorization is what you're allowed to do. It's easy to assume they're the same thing, but they happen in two different places.
The cleanest metaphor is travel. Authentication is your passport or driver's license. It's a portable thing that proves your identity so it can be separated from everyone else's. Authorization is the visa or the ticket. Your ticket says the holder is allowed to board this plane, sit in this seat, board with this group. It has your name on it, but on its own it doesn't prove you're you. In most cases holding the ticket is enough, and the proof stays implicit. If it really came down to it, you'd fall back to the passport with your photo and your biometrics to prove you are who you claim to be. That identity proof is authentication. What the identity is permitted to do is authorization. Two systems, doing two jobs.
MCP only handles authentication
Here's the part that surprises people. The MCP spec is very concerned with authentication. It wants to know who is coming into a server. To our knowledge it is not concerned at all with what you're allowed to do once you're inside.
That's because MCP is a protocol. It's a way of exchanging information between a client and a server to get something done. What happens on the inside, where the business logic lives and where you decide whether a given identity may actually perform a given action, is a growing source of complexity that the protocol deliberately leaves alone. It's exactly the space where a framework like FastMCP has a lot to offer, and it's why the line between the two can feel blurry. Keep the passport and the ticket separate in your head and most of the confusion clears up.
The ways you prove who you are
Before MCP enters the picture, it helps to remember the ordinary ways identity gets proven, because MCP builds on all of them.
The one everyone knows is a username and password. You have a public part anyone can know, your username, and a private part only you should know, your password. Present them together and the system assumes you are that username, because you've demonstrated the secret knowledge attached to it. Most identity systems are some version of that question: can you prove, through something private, that you are the public identity you're claiming?
The one developers know best is the API key. An API key is a self-contained password with no username attached, just a string of characters you carry around and hand to a system. It tells the system who you are, and it often tells the system what you're allowed to do, so authorization is baked right into the authentication. Depending on the system, you may have to supply it on every single call. The service makes no effort to remember you and treats each interaction as a fresh chance to interrogate whether you are who you claim to be. That's fine, and it's a common way for two systems to keep reproving themselves to each other.
Local servers never needed any of this
When MCP first arrived, it was a local-first experience, and that mode is still with us today. The server runs as a process on your own machine and talks to the host directly over JSON-RPC. In that world there is no need for auth, and to this day the standard input/output deployment doesn't contemplate it.
The reason is the trust boundary. Because the server runs on your machine, you already authenticated yourself to that machine, and you're the one who chose to run the process. You're inside the boundary. A huge amount of security thinking is really just deciding where that boundary sits and which side of it you're on. Locally, there's nothing left to prove. You installed and ran the process, which by virtue of running on your machine has access to your machine. So we really hope you took care to read every line of the open-source server you were running. You did that, right? Of course you did.
Just in case you didn't, the next iteration of MCP, back in March of 2025, introduced remote servers as a first-class concept, speaking over HTTP. And that changed everything.
Then remote servers arrived, and auth stopped being optional
In a remote setup the trust boundary disappears. You're reaching an MCP server through your client of choice, and someone else is hosting that server on their machine. They want the server available to you and not to some random stranger, so now there has to be a mechanism for you to prove who you are and a way to check you're allowed in.
The global standard for a human logging into something is OAuth. It's the handshake behind every "log in with Google" or "log in with GitHub" button you've ever clicked. From your seat as a user it feels like one tap of consent, while Google quietly brokers a lot of complicated machinery behind the scenes and acts as a trusted third party vouching for your identity to the site you're entering. The site itself never has to understand how any of it works. It declares that it wants protection and farms out the hard part.
That is not what MCP did at first.
The first attempt was miserable
In the first version, the MCP server was supposed to be both the resource server and the authorization server, to borrow the OAuth vocabulary. In plainer terms, it had to be both the thing being protected and the thing doing the protecting. If you wanted auth on your server, you had to stand up a fully compliant authorization server yourself, which is a genuinely hard thing to build. There are entire companies whose only job is to do this for other people.
The hello-world example in the spec ran to hundreds and hundreds of lines of code, with no real way to shrink it, because you had to present a complete OAuth server just to get basic identity checking. People did not like this. It's around here that FastMCP started digging in and asking whether a high-level framework could hand people these mechanisms instead of making them write all of that by hand. The answer turned out to be yes, and we'll come back to it. Fortunately the spec moved too.
The fix: be a protected resource, not an authorization server
Later, the spec decided the MCP server didn't need to be a full authorization server after all. It just needed to behave as a protected resource within the OAuth framework and let a third-party authorization server, an identity provider, broker the relationship.
Now, instead of presenting that whole complex interface itself, the server can say: I'm a protected resource, go talk to these people, come back with a certificate from them, and I'll let you in. It introduces a new participant into the chain, which sounds like more moving parts, but it means server authors delegate the hard part to someone who specializes in it. For maintenance and complexity on the server, it's a big step forward. That identity provider could be a company like Okta, a server someone runs and fully controls themselves, or Google or GitHub acting in that capacity.
Dynamic Client Registration, and the gap it opens
The spec does expect one particular thing from that authorization server: something called Dynamic Client Registration, or DCR. And this is where it gets both clever and frustrating.
DCR says anyone in the world can show up at your server, register as a client, run the full handshake, and connect. That flies in the face of what people expect from a normal OAuth login. When you sign into a website, you say you're a specific person on a specific machine in a specific browser, and you want your credentials sent back to exactly that place. There's usually some pre-registration and validation to make sure someone can't just invent a name and get authenticated, and to make sure the credentials that come back aren't intercepted and redirected to a malicious actor along the way.
DCR loosens that. Anyone can hit the registration endpoint, say "I'm Jeremiah," and get a credential back that says they're Jeremiah. The trouble is obvious: Radhika could walk up to the same endpoint, also say "I'm Jeremiah," and get the same thing. What's missing is the step that verifies the claim. That gets messy fast, and it's why you want a real identity provider sitting in the middle doing the interception and provisioning. WorkOS, a partner of ours, is one of the few that offer DCR-compliant identity federation, and we leaned on them heavily to understand it. We remember writing to our contacts there more or less saying "it looks like anyone can just log into the server," and getting back, essentially, "yes, that's how it's designed."
Why design it that way? Because your agent might be running anywhere. It could be on some random URL and port in the cloud, in a place you don't even know in advance, and it still needs a way to receive its credentials against the server. DCR pays for that flexibility by giving up a lot of the guarantees a strict handshake provides, and it opens a whole host of novel ways to intercept and abuse the flow. That last part is a topic for its own episode.
What's quietly replacing DCR
DCR is a real, blessed part of the OAuth spec, but to our knowledge no mainstream technology found it useful enough to adopt until MCP came along with this specific "we don't know where the agent is" problem. It was never widely adopted, and it's now being deprived in favor of something called a Client ID Metadata Document, or CIMD.
A CIMD is a new take on dynamic registration that's a little less dynamic. It's a document whose ID is also the URL at which the document is hosted. The one hard requirement is that its ID matches the exact URL you fetched it from. That match is what makes it authentic: you retrieve the document, you confirm the ID inside equals the address you pulled it from, and you know it wasn't handed to you by some third party. It's almost like telling someone they have to go to the address printed in your passport to pick up your passport, which proves you really live there, because only one document can live at that URL.
It's still dynamic in theory. Anyone could point at that document and claim to be that person. But the two-sided free-for-all, where literally anyone can invent any name at any time, goes away. The metadata document can carry restrictions, like which URL credentials are allowed to be sent back to. So the server doesn't just hand credentials to whoever asks. It mails them to a specific, pre-declared place. This is where things get open-ended in ways we're not qualified to opine on, but it's the direction the spec is hardening toward.
Logging in once, not once per tool
The newest push is toward centrally managed identities, in a proposal called SEP-990. It landed in the spec very recently, and it's aimed squarely at enterprises. The idea is that a company can issue credentials on behalf of its employees, so the MCP server acquires those credentials for the employee rather than making the employee self-register or log in over and over.
Picture adding a Salesforce connector. Without this, a page pops up and asks you to log into Salesforce, which is normal, healthy OAuth. With SEP-990 enabled in your organization, the connector is more likely just added for you, and you don't log in at all. Your company already knows who you are because you authenticated once to the control plane, say to Claude, and that's enough, inside the trust boundary, to sign you into Salesforce as you and bring that identity back. It exists to kill the endless re-sign-in every employee does across every tool, even though they already signed into the system that governs all of them.
Turning identity into permissions
Everything so far is authentication. Authorization, what you're actually allowed to do, is where the protocol stays quiet and a framework earns its keep.
OAuth already gives you a couple of hooks. There's the concept of a role: you might carry the role of admin, and it's clean to annotate a tool as available only to admins. There's also the claim, which can be non-standard. Employees at our company might carry a claim like prefect_role that means nothing outside our walls. Most servers will see it and ignore it, but a server we write internally can read it and behave differently.
The key property is trust. Anything embedded in the auth token you were issued after your handshake is trustworthy. You can take it as fact that it really applies to you. Compare that to a tool that just has a field asking "what's your role?" That's untrusted input. A user typing "admin" into a box is not the identity provider vouching for them, and it's obviously not enough to unlock an admin tool. Wherever the information originates, it has to be embedded in the credential, the passport, or you can't act on it. So a big part of what FastMCP does here is take whatever your provider embedded in the credential and give you a clean way to gate tools on it.
If you can't call a tool, you can't see it
There's a subtle decision underneath that. MCP carries an implicit assumption that if you can see a tool, you can call it. So FastMCP takes its own position: if you can't call a tool, you also can't see it. If a tool is restricted to admins and you're not an admin, we generally don't even show it to you.
That may sound obvious, but it's exactly the kind of on-the-margin choice that defines a framework. In the world of REST APIs the opposite is normal. You can see the entire surface area of the API, and you only discover you're not allowed to call an endpoint when you try and get a 403 back. MCP leans the other way, where the server you see is implicitly the server that's meant to be callable by you, and FastMCP honors that expectation.
FastMCP's OAuth proxy
FastMCP doesn't introduce many new auth concepts. It adopts the latest ones, SEP-990 included, so it stays compatible, and most of the core machinery has been in place since FastMCP 2. It got a big overhaul in FastMCP 3, which added authorization on top of authentication.
The piece worth calling out is the OAuth proxy, one of the most popular features in the whole framework. It solves a very practical problem: plenty of identity providers, GitHub, Google, Azure, and countless others, never bothered to expose a DCR-compliant or CIMD-compatible way to register, because they aren't MCP-native. The proxy bridges that gap. FastMCP stands up a small but complete authorization server and presents a compliant interface to the agents trying to register, while behind the scenes it talks to the traditional provider as an ordinary OAuth client application.
The careful part is what happens with the credentials that come back. Those credentials were issued to the MCP server, not to you, and it would be strictly wrong to just hand them over. So the server does a small version of the federation we described earlier. It holds the provider's credentials securely and issues you its own token instead. On your next call, the proxy verifies you're a valid bearer of that token, looks up the identity you have with the real provider, and forwards it so the handshake completes. It's a blessed middleman that protects both the real client and the real server, and keeps that credential from leaking across a boundary it should never cross.
Most people never touch the OAuth proxy directly. But if you use the GitHub, Google, or Azure providers, you're using classes built on top of it. That's one of the most valuable things a framework can do: let you say "I want to use Google to log into my server" and be done, while getting best practices in the background for free. That is the Prefect way. Absorb the nonsense so you can spend your time on the fun parts.
Where Horizon fits
Horizon is a hosting, connectivity, and exposition layer, and it can act as your authorization server, again working with WorkOS as the identity provider underneath. Where it does something distinct is federation across many servers at once.
Say you build a custom server and you also want the GitHub server and the Linear server. Horizon lets you remix those into a single virtual server. That virtual server now requires three identities: the custom one hosted in Prefect, the GitHub one, and the Linear one. Horizon's job is to let you log in once, or with SEP-990 tooling ideally not at all, and then store, or better, federate, those identities so your future calls don't demand new logins. The next time someone shares a remix server with you built on those same backends, you don't reprove your GitHub access, because Horizon sits in the middle as a gateway. When you hit a route that ultimately lands in the GitHub server, it knows to apply your GitHub identity and brokers it for you. That's federated identity, and it's the defining job of an MCP gateway stitching together servers that each have their own auth quirks. SEP-990 takes it one step further by automating the sign-in away almost entirely.
The part nobody has solved yet
Everything we've described assumes an agent is taking on the identity of a human. OAuth is a human-centric standard. It was designed for a person sitting in front of a browser who can click a button that says "yes, this is me."
That assumption is starting to feel wrong. Your agents are not you. It's useful that they can reach the same tools you can, but you'll often want an agent with only a subset of your abilities, or sometimes a different set entirely. Working out how identity and permissions should flow in that world is genuinely hard, and it doesn't map cleanly onto a spec built around a human at a browser. An agent may have no ability to interact with a browser at all. Agent identity, as distinct from human-adjacent identity, feels like the next real frontier, and it isn't solved.
Wrapping up
We managed to talk through pretty much all of it, acronym lookups and all, and we're fairly confident we got most of it right. The short version: MCP auth has been noisy because it's been evolving in public, moving from local trust boundaries to remote servers, from servers being their own authorization servers to being protected resources, and from DCR toward CIMD and centrally managed identities. Underneath the churn, the mechanisms are steadily hardening in a way that gives agents the flexibility they need while preserving the guarantees you need about who's actually on the other end.
Same deal as every episode: if something about auth, agents, or MCP is confusing you, leave a comment. We read them, we respond to them, and we'll turn the good ones into the next conversation.