FastMCP authentication, end to end: OAuth, tokens, and the DCR cliff
Everything that breaks when you put auth on a remote MCP server, and the working code against a real client.
I shipped rapu-agents to production thinking auth would be easy. Auth is the part I’m still fighting.
Here’s my dive, including the fact I still haven’t figured it out.
What I was building
rapu-agents is a FastMCP server. It runs remote, over HTTP, because the whole point is that Claude and other clients reach it from wherever they are.
The agents behind it do real work against real user data, so it can't be an authless server sitting wide open.
The moment you go remote with anything that touches a user's account, you've signed up for OAuth.
With stdio you skip auth entirely. It runs as a local subprocess on your own machine, so nobody needs to check a token. Going remote is exactly what puts the whole OAuth surface on you.
So I needed a user to log in, consent, and have the server trust a token tied to that user.
Pretty standard, I’ve shipped OAuth more times than I can count.
That confidence was my first problem.
The moment it went sideways
I wired it to Google. Google is the IDP everything in my stack already talks to, the credentials were already sitting in my env, and it’s the login my users already have.
The server came up clean. No errors at first.
I pointed Claude Code at it and nothing resolved.
Not a clean issue/problem either.
A 401, then a 403, then the client just refusing to connect at all, then Claude Code unable to resolve anything about the server, like it couldn’t even find the front door.
I’d change something, get a different error, change it back, get the first error again.
If you’ve debugged OAuth before, you know the shape of a normal failure.
Redirect URI mismatch, you fix the redirect URI.
Scope problem, you fix the scope.
The failures didn’t line up.
I wasn’t debugging my config. I was hitting a wall the config couldn’t move, and the wall has a name.
The wrong ideas
The first wrong idea is obvious, and it's the one almost everyone has: point the server straight at Google and expect OAuth to behave like it always has.
It doesn't, because of one specific thing. MCP clients reach for Dynamic Client Registration, where the client registers itself with your auth server on the fly.
Google doesn’t do DCR. Neither does GitHub, Azure, AWS, or Discord. The big IDPs everyone actually uses don’t speak the one thing MCP’s auth flow tries first.
So when you wire a remote auth provider straight at Google, you get exactly what I got.
A server that boots fine and a client that can't finish a handshake, surfacing as 401s and connect failures that all look like your fault.
The framework fix is OAuthProxy, the FastMCP class that sits between the MCP client and a non-DCR provider. It fakes the DCR interface the client wants while holding your pre-registered Google credentials on the other side. There's a GoogleProvider subclass so you’re not wiring the proxy by hand. That part is solvable and documented.
The second wrong idea was assuming there was a shortcut. There isn't, not for Claude-targeted servers.
You can't fall back to a static API key, because user-pasted bearer tokens aren't supported. You can't do a pure machine-to-machine client_credentials grant either. Every connection needs a real user going through a real consent screen.
The move every engineer reaches for when OAuth gets annoying, just hand it a token, isn’t allowed here.
Then there's the gotcha you'd never guess until it's eaten an afternoon. Claude Code is a native client. It does its OAuth callback on a loopback redirect.
Your server has to accept the loopback host and ignore the port:
allowed_client_redirect_uris=["http://localhost:*", "http://127.0.0.1:*"]The fix that worked, except it didn’t
rapu-agents auth is still not where I want it. I got past the DCR wall with the proxy, I sorted the loopback redirect, and Claude Code still doesn't cleanly resolve the server end to end.
The tooling around MCP auth is young. FastMCP has shipped a lot here fast. Static Client Registration and CIMD both landed to take some of the DCR pain away, and the spec itself is still moving.
Some of what bites you this month gets fixed next month and replaced by a new sharp edge.
So I'm not going to fake a resolution. What's holding it together right now is the proxy plus a lot of careful redirect handling, and the honest status is "connects under the conditions I've nailed down, fragile outside them."
Double work
On the same product, the client side needed Google Search Console and GA4 wired in over OAuth.
Different surface, different reason, same Google.
Token refresh you handle yourself, an API that fights you on the details.
So the auth tax didn't hit me once on the MCP server.
It hit me twice on one product, server side and client side, both times wearing the same Google login I thought I already understood.
It feels like trying to understand Auth & OAuth as a junior again.
Auth isn't a line item on an MCP build. It's a recurring cost that shows up every place a user's identity has to cross a boundary.
The one thing I’d tell past me
The hardest part of MCP is auth. Everything else is easy. I wish someone had told me that before I picked Google and assumed it’d take an afternoon.
If you want the version you can act on tonight: budget auth as its own project, not a config step at the end. It isn’t the last 10% of the build. It’s a parallel build, and on the providers you already use it’s the one most likely to still be open when you thought you were shipping.
Pick your IDP for whether it cooperates with MCP, not for whether it's already in your env. Past me reached for the familiar login. That's exactly the reach that cost the most.
If you've hit the DCR cliff with a different provider, or found a fix I haven't, drop it in the comments. I'm still not fully clear of this one.



