Product
Platform
Platform
Platform
Developers
Quickstarts
Resources
Explore
Pricing
Download
get a demoLogin

Securing an MCP server starts with a question the protocol does not answer for you: who is making this call? This webinar builds both answers in Python. Dan Moore stands up an unauthenticated MCP server, calls it with curl and from Claude Desktop, then rebuilds the same server behind OAuth with FusionAuth as the authorization server. You will see the five steps of the MCP authentication flow, the two ways an MCP client can register with an authorization server, what a valid access token must contain before your server honors it, and where the security work the specification does not cover still has to happen.
.png)
.png)
Hi, folks. Thanks for joining me today. I'm going to be talking about securing your MCP server. We're going to go through some slides, go through some code, and at the end of it, you'll have learned how you can stand up an unauthenticated MCP server and how you could integrate with a solution like FusionAuth to secure your MCP server.
Here's what we're going to cover today. We're going to have a brief overview of MCP, which is the Model Context Protocol. We're going to look at an unauthenticated MCP server and what that looks like, including calling it. Then we're going to talk about MCP authentication and some of the variants of that. And then we will stand up and demo an authenticated MCP server.
MCP stands for Model Context Protocol, and this is a way for AI tools to interact with your systems. You have AI tools like chat solutions, agentic IDEs, and other AI applications that are all on the left-hand side here, and they're all looking to interact with your data, your functionality. MCP is the standardized protocol. It came out in late 2024. There have been four different versions, the latest coming out in July 2026. It provides a standardized way for the AI applications on the left to interact with arbitrary data, tooling, and APIs on the right. So that could be databases, could be development tools like GitHub or Sentry, or it could be SaaS tools, or it could be your own APIs.
MCP clients, which again are those things on the left, have some well-known names. Anthropic was the originator of the MCP protocol. OpenAI is also compatible, and AI agents, as indicated by the robot, are also able to speak the Model Context Protocol.
There are a couple of different flavors of ways for your AI systems to interact with your functionality. You could choose a command line interface and have the AI agents interact with a CLI tool written in whatever language you want. You could have it directly interact with an API — a RESTful API, having an OpenAPI spec, is a perfectly valid way to do that. But Model Context Protocol has some additional benefits. It has some additional security benefits. It's a standard, and many tools know how to use that — many MCP clients know how to use it. And it also provides context that can help guide an agent in a way that the API or the CLI might not actually handle.
Within MCP, there are two flavors. There's local and remote MCP servers. Local servers live on your system, and you connect them to your MCP clients via a process. So it's like a pipe, basically, and they talk to each other over that process pipe. Remote MCP servers, on the other hand, are connected to over HTTPS, over TLS, and live out there on the internet. Obviously, when you have a remote MCP server, that opens up more flexibility as to where the MCP client lives. It's very hard for an MCP client that is on a remote system to interact with a local MCP server, whereas the opposite is not true.
The next thing to think about here with MCP is, is it authenticated or unauthenticated? An unauthenticated MCP server doesn't act on behalf of anybody. It is totally — that's what I'm looking for — it is 100% just out there. Good examples of unauthenticated MCP servers might be public datasets, where it doesn't vary from person to person and you don't want to gate access at all, or documentation. We actually have a documentation MCP server with FusionAuth that is very highly trafficked and has helped me and plenty of other people answer our questions about the product. An authenticated MCP server, on the other hand, can — because it has context about the entity calling it, whether that's a person, an agent, or a program — give back different data based on that identity and the roles or authorization that identity has.
The final kind of choice is whether the entity making the call is doing it on its own recognizance. Is it an agent that is tasked with some external task and not connected to a human being at all? Or are you acting on behalf of an existing human being? These are all great ways to help your agent get the data and context it needs. We're going to focus on the on-behalf-of, authenticated, remote MCP path today.
Another thing that's worth noting is that the Model Context Protocol specification talks about what the MCP client needs to look like. It talks about the MCP server. It talks about the auth server, which issues tokens, and it basically handles a lot of the layer one heavy lifting. So if you talk to someone and they say, "Yeah, I have a Model Context Protocol compatible authentication server," that's living in layer one. There is a layer behind the MCP server that I'm not going to talk about much today, but it's something that's very important. It is layer two. It's everything behind the MCP server that is being accessed on behalf of the MCP client by the MCP server. This could be reaching out to different APIs, different databases, collecting multiple different requests and gathering them together to get back the data or the functionality that's needed, or to trigger the functionality that's needed. The reason it is not covered by the MCP spec, as far as I can tell, is one, it's going to be super business-logic specific. And two, a lot of the things that an MCP server is getting access to will already have some kind of authorization model.
So let's talk about unauthenticated MCP. This is MCP where, again, like a documentation site, you really don't care who is making the request. You're going to give back the same data. We're going to dig in and actually look at some code. So this is a GitHub repo, and I apologize — that's FusionAuth is what that is. It's a little cut off there. Let me make that a little bit bigger, and I'm going to share my screen.
Alright. So I've already cloned this, but there's the Git repo. I'm inside the repo right now, and we have two different directories here. First is the unprotected, or unauthenticated, MCP server, and then we have the protected remote MCP server. They're both actually talking to an MCP server that is running over HTTP on localhost, just because that's the simplest thing to demo.
So let's look at some files inside the unprotected local MCP server directory. We have a pretty simple MCP server — actually, let me edit this. We're using a framework called FastMCP, and all we have is one tool here. This tool is just going to give us a name. And this context from lines 19 to 21 is one of the things that makes MCP so powerful, because it can provide additional context that an LLM can ingest.
I'm going to start the MCP server. And then what we're going to do is first, we're going to see what a raw call looks like — like a call using curl — and then we're going to see what it looks like inside Claude Desktop. The first thing I'm going to do is, I actually wrote out the curl script in here. If we just go ahead and run that, this is what the JSON-RPC call looks like that MCP is built on. You can see that we have JSON that comes back, and it is showing us the "hello world" text. We can pipe all that through, and you can see that it looks something like this. This is all generated by the FastMCP framework — you don't have to do any of that as a developer.
So now let's go ahead and call the MCP from Claude. First, I'm going to stop Claude Desktop, and then I'm going to open the Claude config file, which I already have here. You can see I was testing in the past, but I'm just going to cat it. Oh, wait — that's the wrong thing. So the claude_config_locations.txt has the location of the Claude config file for different systems, Windows or Mac. And that's what I wanted. Then, this is what you expect — what Claude is going to expect. This basically tells us, hey, connect to this MCP server. There's no client credentials or any OAuth config. This is an open server. And one thing I've learned is you want to pipe this through jq to see that it is a valid JSON file, because if you don't have valid JSON and Claude Desktop starts up, you'll lose some configuration.
So I'm going to stop sharing my terminal, and I'm going to share Claude Desktop. I'm starting Claude Desktop right now. It's wishing me a good afternoon, which is kind. And then I'm going to ask it to use the tool that we just added. It's going to reach out and look at the available MCP servers and tools that are available. It's asking — I'm going to allow this once, so it doesn't get cached. And there you are: hello, world. Congratulations. We've just gone through an unauthenticated MCP server and installing it in Claude Desktop.
So this is what we saw. You always have to have a backup of screenshots when you're doing a demo. I'm going to clean up. I'm going to stop Claude real quick, and I'm also going to clean up that MCP server.
Okay. So that's an unauthenticated MCP server, which has some uses but is probably not as useful as an authenticated MCP server. So let's talk about authentication in the Model Context Protocol. Basically, the MCP client connects to the MCP server — again, we're just focusing on layer one here, we're not focusing on layer two. The MCP server is going to redirect the MCP client to the auth server, where there's going to be some authentication event that happens. Then the auth server will issue a token. And then the MCP server will receive that token from the MCP client, validate that token, and then give the information that is requested, or take the action that's requested.
There are five steps to have this happen, and we're going to walk through each of these five steps in a little more detail. The first step, of course, is the MCP server needs to be running, so let me go ahead and start that up.
The next thing that needs to happen is the MCP client needs to know about the MCP server somehow, and that can happen via configuration. I'm not sure exactly what discovery mechanisms are available, but definitely within some of the MCP clients like Claude or OpenAI, a central administrator can set this up. But at some point, the MCP client needs to learn about the MCP server — needs to know about it, needs to know what URL it exists on.
Then the MCP client goes to the MCP server and makes a request, or reads the metadata, and learns about the auth server that this MCP server has delegated authentication to. The MCP server doesn't need to understand who the user is, doesn't need to issue tokens. It just needs to delegate that to an auth server like FusionAuth.
Let's go ahead and look at the RemoteAuthProvider class, which is part of the FastMCP specification. That is how that happens here. So I'm in the protected remote MCP directory now. I have edited the FastMCP MCP server, which is a little more complicated than the previous one. The important thing here, from the perspective of authenticating the MCP clients, is this RemoteAuthProvider configuration. What we're doing here is we have a method to verify the token. We have mentioned on line 59 which authorization servers we are accepting for this MCP server. In this particular case, when I started up this MCP server, I passed in an environment variable that pointed to a FusionAuth instance. And we're also talking about which scopes we're going to ask for, or which scopes we might be interested in. A scope is just basically a coarse permission.
If we look at the tool on line 70 and a little bit beyond, we have that same description field, which is more precise. Here we're getting an access token that is passed to us, and then we are going to go ahead and retrieve the user information from FusionAuth via the access token using the userinfo OpenID standard endpoint. But this is where the magic would happen in your MCP server. After you've authenticated the user, you would probably look at this access token, look at their username, look at their sub claim, and then go ahead and do the requests against the database in the way that you would need to, or whatever other system like APIs or whatever system you need to call.
So back to the presentation. After the MCP server has said to the MCP client, "Hey, here's the authorization server I have," the next step is for the MCP client to basically register as an OAuth client in the authorization server. There are a couple of different ways we can do this.
The first is what I think of as the bouncer method. The bouncer method basically doesn't require a lot from the MCP client. This is kind of a just-in-time registration, where the MCP client presents something that the authorization server can accept and create an OAuth client inside it. This is just like a bouncer can look at your ID and determine whether or not you should get in without really knowing who you are, without having any prior interaction with you. In this case, there are two protocols that help with just-in-time registration. That would be Client ID Metadata Document, which is the newer version that is the future, and Dynamic Client Registration, which was originally the way to do this in the earlier versions of the MCP specification but is now basically kept around for backward compatibility.
The other way to register is more like an invitation to a dinner party. This is where you're already known — the MCP client is already known by the authorization server before they ever make that initial request. This is a first-class citizen in the Model Context Protocol, and it's called client preregistration.
So we're going to go ahead and do client preregistration right now, because it's kind of a simpler one to demo, and I'm going to walk through that on the terminal. We are already in the protected remote MCP directory because, again, I started the MCP server a little bit ago. Now we're going to go into setup. Basically, there is a Python script that has been set up with an API key and everything to run against FusionAuth's sandbox environment. Let me look here real quick. We have a Python script that's being called by a shell script because this is easy. This is a running FusionAuth instance. It's running the latest version. You can go log into there right now. It is reset every — I don't know — every couple of hours. So don't put any special data in there, but it's fine for doing a demo. And that's why I'm not worried about this API key being leaked, because it's already publicly available.
So what I'm going to do is run this, and I'm going to give it a name. It creates a bunch of things, and it's basically calling some proprietary FusionAuth APIs to set up an OAuth client. This may look familiar to you. We are going to paste this into our Claude config, and it's similar to what we saw for the unprotected MCP server, but now we actually pass some OAuth information in. So I'm removing the unauthenticated one, and I'm adding in the protected one. I'm going to make sure that JSON looks okay. JSON looks good.
Alright. The next thing we're going to do is I'm going to stop sharing my screen and share the presentation again. So the next thing we're going to do is we're going to authenticate. This is part of that flow. The first couple of steps take place — well, the registration might need to happen once for each MCP client. It does depend on the MCP client implementation. But after an MCP client has been registered in the OAuth system, then you need to make sure that you authenticate. This is what makes it part of the on-behalf-of flow, because I'm going to authenticate, and then I'm going to get an access token that has my information in it. And the MCP client is now going to be acting on my behalf. This tends to require me to grant certain scopes.
So I'm going to now start my Claude Desktop, and we'll see what happens after that. Actually, I need to — alright. So I'm starting Claude Desktop, and I'm going to share a different screen, because I have a different default browser. So it pops up my default browser, and then it prompts me to log in. Now it's taking me through the entire login flow. There was just a prompt to set up a passkey; that wouldn't necessarily happen for you. And here's the consent screen, so I can choose which scopes this MCP client is going to get. I'm going to allow all the scopes, and then it says authorization successful. This happened when I started up Claude Desktop and it read that there was an MCP server with OAuth configuration.
Now I'm going to go back to my Claude. So now Claude has in its innards an access token that it can use to call the MCP server. Let's go ahead and test the MCP server out. "Please call…" — so that's the same tool name that we have within the unauthenticated MCP server, but it's a different MCP server at this point. It's looking — I'm going to allow this again once, and it's calling get_name, as you'd expect. The name of this particular user I logged in as is Erlich Bachman. And so, therefore, we've successfully called an authenticated MCP server from Claude Desktop.
Back to the presentation. Again, here's my backup, but you saw this live. Sometimes you might need to troubleshoot this, especially if you're doing early development. This is going to vary depending on what your MCP client looks like, but always think about things like logging out of your authorization server. I need to update that logout URL — that's not the correct one anymore. Making sure your configuration is valid JSON. If you're using mcp-remote, which is a way for Claude Desktop to access remote servers, it caches tokens, so make sure you always stop Claude.
So that's it. Congratulations. You have successfully learned about MCP. You've learned about unauthenticated and authenticated MCP servers. And if you've been following along with the code, then you've done it yourself.
Real quick cleanup. I'm not going to run through this right now, but I did want to show you the token. This is an example of a token that is generated by FusionAuth that is passed to the authentication server. Important pieces of this token: the audience must include the MCP server URL. That's part of the specification, and if an MCP server is getting a token that doesn't include this, then it should not proceed further. The expiration time is the number of milliseconds — or I think the number of seconds — since 1970. This is a key part of using OAuth in the Model Context Protocol. All these tokens are short-lived, which means that the blast radius if one gets stolen is small. The issuer, in this case, is example.com, but it could be something else — it could be sandbox.fusionauth.io. This should match up to the configuration the MCP server sent over when the MCP client first interacted with it. And then finally, the scope: in this particular token, the scope is get_name, which is the scope associated with the tool in the FastMCP server.
So here's some additional resources. Thank you very much for watching this video. I hope that you learned about MCP servers and that it was helpful to you. Thanks again for attending, and you can learn more about MCP and access tokens and everything else at oauth@fusionauth.io.