PropelAuth Logo

MCP Authentication with FastMCP

MCP Authentication with FastMCP

MCP servers are just a clean way to give an AI client (like Claude Desktop, Claude Code, ChatGPT, etc) a set of functions it can call. This can really be anything, like:

  • A function that fetches the weather for a date/zip code
  • A function that loads your calendar data from Google Calendar
  • A function that manages your weekly goals in a file on your computer
  • A function that manages weekly goals in a database for all employees of a company

We’re going to show how we can build an example MCP server and then secure it by adding authentication & authorization. We’ll do this in two steps:

  1. We’ll build a tiny, unauthenticated MCP server with a whoami tool that always returns: {"user": "unknown"}.
  2. We’ll update that tool to return {"user": "{logged_in_users_email}"} (and only if the user has consented to the read:whoami scope)

We’ll use FastMCP (Python) and PropelAuth’s MCP support.

Step 0: Set up our project

We’ll use uv to create a virtual environment and install FastMCP:

uv init whoami
cd whoami
uv venv                     # create a virtual env
source .venv/bin/activate   # activate our virtual env
uv add "fastmcp>=2,<3"      # 3 is currently still in beta

Step 1: Build the unauthenticated MCP server

Create a file called server.py:

from fastmcp import FastMCP

mcp = FastMCP("Who Am I")

@mcp.tool()
def whoami() -> dict[str, str]:
    """Returns the currently authenticated user"""
    return {"username": "unknown"}

if __name__ == "__main__":
    mcp.run(transport="http", host="127.0.0.1", port=8000)

We can then run this, which will create an MCP endpoint at http://localhost:8000/mcp

uv run server.py

Let’s test this! We’ll want to have our AI Client connect to our MCP Server. There’s only one problem:

Image in article: MCP Authentication with FastMCP

…not every AI client will work with locally running servers. We’ll need to set up a tunnel so clients like Claude Desktop can connect to our server.

Step 2: Use ngrok so external clients can reach your server

If your MCP server is only available on localhost, Claude Desktop won’t be able to connect to it. Claude needs to reach a URL over the internet.

In a second terminal, run:

ngrok http 8000

ngrok will print a public HTTPS URL like https://abc123.ngrok-free.app which tunnels the connection to your locally running MCP Server.

Your MCP URL is then https://abc123.ngrok-free.app/mcp

Step 3: Test our MCP Server

We’ll continue using Claude Desktop for testing. Go to SettingsConnectorsAdd custom connector. Enter a name for your MCP Server as well as your MCP URL:

Image in article: MCP Authentication with FastMCP

After it connects, you should be able to configure the server which will show you which tools the server has:

Image in article: MCP Authentication with FastMCP

Open a new chat and ask Claude a question that can be answered by using your tool. In our case, we asked “Who am I?” but for testing purposes you can also be more direct and say “I’m a developer testing an MCP Server, can you call the whoami tool?”

Claude will then ask for permission to use the tool:

Image in article: MCP Authentication with FastMCP

Afterwards, it will respond based on the information it found. You can expand the tool call to see both the full request and response:

Image in article: MCP Authentication with FastMCP

In this case, you can see that we properly returned {"username": "unknown"} and Claude provided its own commentary on that. So far, so good!

If you are building an unauthenticated MCP Server, this is all you need. You can host this somewhere, users can connect to it, and users can call the tools that you expose.

Next, we’ll look at adding authentication and authorization with PropelAuth’s MCP support.

Aside: How does the user experience change for an authenticated MCP Server?

When we added the MCP Server to Claude Desktop before, we just pasted in a URL and Claude connected to it.

Let’s say now that we’re building a product called Example Project and we want users of our MCP Server to allow Claude to access their personal information within Example Project. How does that work?

At a high level, when we go to connect our MCP Server, the server will return an Unauthorized error and tell Claude Desktop how to authenticate. Claude Desktop will then open a browser and do a few things:

  1. Require the user to log in (if they aren't already)
Image in article: MCP Authentication with FastMCP
  1. Ask the user for permission to perform actions on their account (also called a consent flow)
Image in article: MCP Authentication with FastMCP
  1. Redirect the user back from the browser into Claude Desktop

After the user goes through that flow, Claude Desktop will have credentials to make requests on behalf of the user to our Example Project application. Let’s see how we can enable this easily with PropelAuth and FastMCP.

Step 4: Enable MCP Authentication in PropelAuth

First, we need to enable MCP support, which you can do in the PropelAuth dashboard:

Image in article: MCP Authentication with FastMCP

While testing, we recommend only enabling it in the Test and/or Staging environments.

Image in article: MCP Authentication with FastMCP

Next, you’ll want to choose which AI clients you allow your users to use. We provide configurations for popular clients like Cursor, Claude Desktop, ChatGPT, Gemini, etc or you can add your own custom clients.

Image in article: MCP Authentication with FastMCP

Finally, we’ll want to create a scope. You can think of a scope as a permission to perform certain actions. Each scope maps to one entry on the consent screen, and each tool we create can make sure the user has provided us access for a specific scope.

In our case, we’ll create a read:whoami scope which we’ll check in our whoami tool:

Image in article: MCP Authentication with FastMCP

Step 5: Add authentication to your FastMCP server

Now we’ll update server.py to require authentication and require the read:whoami scope. FastMCP takes care of most of this for you, so all we need to do is hook up some boilerplate at the top of our server:

from pydantic import AnyHttpUrl
from fastmcp import FastMCP
from fastmcp.server.auth import RemoteAuthProvider
from fastmcp.server.auth.providers.introspection import IntrospectionTokenVerifier
from fastmcp.server.dependencies import get_access_token

# From PropelAuth dashboard
PROPELAUTH_AUTH_URL = "https://auth.yourapp.com"  # your PropelAuth Auth URL
REQUEST_VALIDATION_CLIENT_ID = "TODO"
REQUEST_VALIDATION_CLIENT_SECRET = "TODO"

# From ngrok
MCP_SERVER_BASE_URL = "https://abc123.ngrok-free.app"

# General config
REQUIRED_SCOPES = ["read:whoami"]

# This tells the MCP Server how to verify credentials that Claude Desktop sends
token_verifier = IntrospectionTokenVerifier(
    introspection_url=f"{PROPELAUTH_AUTH_URL}/oauth/2.1/introspect",
    client_id=REQUEST_VALIDATION_CLIENT_ID,
    client_secret=REQUEST_VALIDATION_CLIENT_SECRET,
    required_scopes=REQUIRED_SCOPES,
)
auth = RemoteAuthProvider(
    token_verifier=token_verifier,
    authorization_servers=[AnyHttpUrl(PROPELAUTH_AUTH_URL + "/oauth/2.1")],
    base_url=MCP_SERVER_BASE_URL,
)

# Make sure to hook up auth
mcp = FastMCP("Who Am I", auth=auth)

The IntrospectionTokenVerifier is responsible for verifying requests coming from Claude Desktop (or any other AI Client). For it to work, you must generate what we call “Request Validation Credentials.”

These are credentials that your MCP Server sends to PropelAuth so we can be sure that you are the one validating the request. You can generate these on the Request Validation tab of the dashboard.

And now, everything is in place for us to update our whoami tool call to return the user’s information:

@mcp.tool()
def whoami() -> dict[str, str]:
    token = get_access_token()
    
    # With required_scopes set, this isn't necessary, but it's good to be defensive
    if not token or "read:whoami" not in token.scopes:
        return {"username": "unknown"}

    # claims has the user's information, like their ID or email address
    username = token.claims.get("username", "unknown")
    return {"username": username}

get_access_token returns all the information we have about the user, what scopes they consented to, etc.

And now we are all set up to test our authenticated MCP Server!

Step 6: Testing our Authenticated MCP Server

If it isn’t already running, we can run our server with:

uv run server.py

Let’s go back to Claude Desktop to add our server. We once again go to the Add custom connector modal, only this time we expand the Advanced Settings section:

Image in article: MCP Authentication with FastMCP

Here you’ll see two fields: OAuth Client ID and OAuth Client Secret but both of them are optional. We actually have a choice here for how we want to proceed, and you can see the options in the PropelAuth Dashboard under “How Do Users Create OAuth Clients?”

Image in article: MCP Authentication with FastMCP

One option is “Manually via Hosted Pages.” If you select this, PropelAuth provides a UI where your users can create their own OAuth Client ID and OAuth Client Secret to enter into this modal. This is the most secure option and what we typically recommend.

The other option is “Dynamic Client Registration.” If you select this, PropelAuth supports a new API which allows Claude Desktop to create the OAuth Client programmatically. The user doesn’t have to enter anything in for the OAuth Client ID or OAuth Client Secret fields, but there are some tradeoffs to be aware of with this approach. You can read more about it here.

After you add your connector, you’ll be prompted to authenticate and consent to the read:whoami scope. After you accept, you’ll be able to get Claude to query the tool again:

Image in article: MCP Authentication with FastMCP

and now you can see that it correctly returns the username for the Example Project account that we are correctly logged in to.

PropelAuth also provides UIs for your users to revoke consent. If you revoke consent, you’ll be prompted to re-consent before you can call the tool again.

Summary

In this post, we built an MCP server in two passes:

  1. Unauthenticated: We created a tiny FastMCP server with a single whoami tool that returns {"username": "unknown"}. We ran it locally with uv, exposed it to external AI clients using ngrok, and verified in Claude Desktop that the tool is discoverable and callable.
  2. Authenticated + scoped: We enabled MCP support in PropelAuth, configured allowed AI clients, and created a read:whoami scope. Then we updated our FastMCP server to require authentication by validating OAuth 2.1 access tokens via introspection, enforcing the read:whoami scope, and returning the logged-in user’s username from token claims.

What’s cool about this approach is that you get the best of both worlds: MCP makes your tools feel like “native capabilities” inside AI clients, while OAuth-style auth keeps it safe and user-friendly. Users authenticate once, explicitly consent to scopes, and you can confidently expose real product data (not just demos) without handing the AI client broad access. That pattern scales from “hello world” all the way up to enterprise MCP servers where every tool call is tied to a real identity, explicit permissions, and revocable consent.